Preface
这次比赛蛮简单的,就是黄题有点多,少了区分度。
而且 Sigma Problem
Another Sigma Problem
Yet Another Sigma Problem
是什么奇妙的题目名称?
Sigma Problem
Another Sigma Problem
Yet Another Sigma Problem
\(\texttt{\scriptsize Yet \footnotesize Another \normalsize Sigma Problem}\)
真好玩
F 题题面看上去很可做,有点迷惑人,实际上对我来说 G 题才是更能做的(也确实能一眼做法)。
A - Buildings
点击查看代码
#include<cstdio>
using namespace std;
const int N=105;
int n,h[N];
int main()
{
scanf("%d",&n);
int pos=-1;
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
if(h[i]>h[1]&&pos==-1) pos=i;
}
printf("%d\n",pos);
return 0;
}
B - AtCoder Amusement Park
高桥出场。
点击查看代码
#include<cstdio>
using namespace std;
const int N=105;
int n,k,a[N];
int main()
{
scanf("%d%d",&n,&k);
int rest=0,ans=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(rest<a[i]) rest=k,ans++;
rest-=a[i];
}
printf("%d\n",ans);
return 0;
}
C - Sigma Problem
好家伙,卡我二十多分钟,而且两次都莫名其妙 TLE 一个点 代码挂了,不怪 AtCoder。
将原数列排序,后每个数与前面的数组合的情况分为两部分:一部分的数与当前数相加小于 \(10^8\),另一部分大于等于 \(10^8\)。对于后一部分,需要对每个和都减去一个 \(10^8\)。
两个部分的交界点可以用二分或双指针维护,我用的是双指针。
算上排序的时间复杂度是 \(O(n \log n)\)。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3e5+5,P=1e8;
int n;
long long a[N],sum[N],ans;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
sort(a+1,a+n+1);
int pos=-1;
for(int i=1;i<=n;i++)
{
ans+=a[i]*(i-1)+sum[i-1];
if(a[i]+a[i-1]>=P)
{
if(pos==-1) pos=i-1;
while(pos>=0 && a[pos]+a[i]>=P) pos--;
ans-=1ll*(i-pos-1)*P;
}
sum[i]=sum[i-1]+a[i];
}
printf("%lld\n",ans);
return 0;
}
D - Another Sigma Problem
比上一道更简单,设 \(a_i\) 有 \(l\) 位,与前面的数组合相当于前面的数乘以了 \(10^l\)。将前面的所有数的和乘以 \(10^l\) 再加上 \(a_i\) 被算了 \((i-1)\) 次,就是其与前面组合的答案,对每一个 \(a_i\) 全部加起来即可。
#include<cstdio>
using namespace std;
namespace IO{
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^'0');ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;
const int N=2e5+5,P=998244353;
int n;
long long a[N],sum[N],ans;
long long ten[25];
int main()
{
ten[0]=1;
for(int i=1;i<=15;i++)
ten[i]=ten[i-1]*10%P;
read(n);
for(int i=1;i<=n;i++)
{
read(a[i]);
int l=0; long long t=a[i];
while(t) l++,t/=10;
ans=(ans+sum[i-1]*ten[l]%P+a[i]*(i-1)%P)%P;
sum[i]=(sum[i-1]+a[i])%P;
}
write(ans,'\n');
return 0;
}
E - Yet Another Sigma Problem
这是道原题,准确来说,这是这道题的简化版,也不知道为什么有些人没做起。
建立字典树,记录每个节点被经过的次数,然后每次在字典树中匹配字符串时把路径上的经过次数值加起来就是它与前面所有字符串的 LCP(最长公共前缀)之和。
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3e5+5;
int n; char str[N];
long long ans;
int trie[N][30],idx;
int cnt[N];
void Insert(char s[])
{
int p=0,len=strlen(s);
for(int i=0;i<len;i++)
{
char ch=s[i]; int num=ch-'a'+1;
if(!trie[p][num]) trie[p][num]=++idx;
p=trie[p][num];
cnt[p]++;
}
return;
}
long long Query(char s[])
{
int p=0,len=strlen(s);
long long res=0;
for(int i=0;i<len;i++)
{
char ch=s[i]; int num=ch-'a'+1;
if(!trie[p][num]) break;
p=trie[p][num];
res+=cnt[p];
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",str);
ans+=Query(str);
Insert(str);
}
printf("%lld\n",ans);
return 0;
}
G - Merchant Takahashi
一眼 DP,做法也很模板,几乎也是一眼(好像是因为我之前做过类似的题)。
先考虑暴力 DP,设 \(f_{i,j}\) 表示前 \(i\) 轮走到城镇 \(j\) 的最大收益,那么有:
\[f_{i,j} = \max_{k=1}^{n} \{f_{j,k} - c \times \lvert j-k \rvert\} \]第一维可以压掉,变成:
\[f_i = \max_{j=1}^{n} \{ f_j - c \times \lvert i-j \rvert \} \]时间复杂度 \(O(nm)\),过不掉此题,考虑优化。
对于每一个位置 \(i\),把 \(f_j\) 分为 \(j \le i\) 和 \(j \ge i\) 两部分(不要管这两部分有重叠的问题)。左边部分收益随距离的变化量(路费)分别为:
\[-(i-1)c, -(i-2)c, \cdots, -2c, -c, 0 \]转化一下,得到:
\[(1-i)c, (2-i)c, \cdots, ((i-2)-i)c, ((i-1)-i)c, (i-i)k \]容易发现,在上述 \((x-y)c\) 的形式中,\(x\) 为左侧其它位置各自的下标,\(y\) 为所求位置下标。
所以对于左侧的 \(f\),只需要维护 \(f_j+jc\)(JC!)的最大值即可,在 \(i\) 左侧求得这个的最大值以后,再减去 \(ic\) 就是向左走的最大收益。
同理,右侧只要把求左侧的过程翻转一下就可以了。路费分别为:
\[0, -c, -2c, \cdots, -(n-i-1)c, -(n-i)c \]对于右边,维护 \(f_j+(n-j+1)c\),在 \(i\) 右侧找最大值再减去 \((n-i+1)c\) 就是向右走的最大收益。
对左右两边取最大值即为 \(f_i\)。
维护左右侧的最大值可以用最大值树状数组,一个找前缀最大值,一个找后缀最大值。
注意一定要将相关变量初始化为极小值(除了 \(1\)),代表只能从 \(1\) 出发。
#include<cstdio>
#include<algorithm>
using namespace std;
namespace IO{
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^'0');ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;
const int N=2e5+5,M=2e5+5;
int n,m;
long long k,t[M],p[M];
long long ans;
struct BIT{ //前缀最大值树状数组
long long c[M];
void Init()
{
for(int i=0;i<=n;i++)
c[i]=-1e18;
return;
}
inline int lowbit(int x){return x&-x;}
void update(int x,long long y)
{
for(;x<=n;x+=lowbit(x))
c[x]=max(c[x],y);
return;
}
long long query(int x)
{
long long res=-1e18;
for(;x;x-=lowbit(x))
res=max(res,c[x]);
return res;
}
}fl;
struct BIT_r{ //后缀最大值树状数组
long long c[M];
void Init()
{
for(int i=0;i<=n;i++)
c[i]=-1e18;
return;
}
inline int lowbit(int x){return x&-x;}
void update(int x,long long y)
{
for(;x;x-=lowbit(x))
c[x]=max(c[x],y);
return;
}
long long query(int x)
{
long long res=-1e18;
for(;x<=n;x+=lowbit(x))
res=max(res,c[x]);
return res;
}
}fr;
long long f[N];
int main()
{
read(n),read(k); read(m);
fl.Init(),fr.Init();
for(int i=1;i<=n;i++) f[i]=-1e18;
f[1]=0; fl.update(1,k),fr.update(1,n*k);
for(int i=1;i<=m;i++)
{
read(t[i]),read(p[i]);
f[i]=max(
fl.query(t[i])-t[i]*k,
fr.query(t[i])-(n-t[i]+1)*k
)+p[i];
fl.update(t[i],f[i]+t[i]*k),
fr.update(t[i],f[i]+(n-t[i]+1)*k);
ans=max(ans,f[i]);
}
write(ans,'\n');
return 0;
}
标签:AtCoder,ch,Beginner,Contest,int,pos,long,Problem,Sigma
From: https://www.cnblogs.com/jerrycyx/p/18544378