Codeforces Round 970 (Div. 3)
Sep/01/2024 22:35UTC+8 length 02:15
好闲啊,还要写 div3 的复盘,就当听歌的同时练习翻译兼打字了。总而言之还是太菜了
# | Who | = | Penalty | * | A | B | C | D | E | F | G | H |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1624 | BaSEc1d | 6 | 250 | +00:04 | +00:19 | +00:24 | +00:34 | +01:17 | +01:32 |
因为开学前几天怕打扰室友睡觉影响人际关系,做完 D 题之后就敲键盘不敢太快了,然后理论上想做完 E 之后就睡,但是发现 F 是水题。
G. Sakurako's Task
给你一个长为 \(n\) 的序列 \(a\),可以选择两个数 \(i,j(i\neq j)\),满足 \(a_i\geq a_j\),然后进行赋值 \(a_i=a_i-a_j\) 或 \(a_i=a_i+a_j\)。可以进行上述操作无数次,要求最大化 \(mex_k\)。其中 \(mex_k\) 是指序列中第 \(k\) 大没有出现的自然数(自然数包括 \(0\))。例如 \(mex_2(\{0,2,4\})=3\)。
\(1\leq n\leq 2\times 10^5,1\leq k\leq 10^9\)
显然无论 \(mex_k\) 中 \(k\) 取多少,我们都要尽量让 \(a\) 中元素尽可能小且不重复。
发现如果 \(a\) 中有 \(1\),那么可以将 \(a\) 轻松变成 \({0,1,2,3,4,\dots}\)
我们现在需要判断能不能构造出一个 \(1\) 出来,可以大胆猜测如果 \(a\) 中所有元素的 \(\rm gcd\) 为 \(1\),就能构造出 \(1\) 来。
额我不想证了,因为让我证我估计短时间也证不出来。根据拓展欧几里得,\(ax+by=\gcd(a,b)\) 的解有无数个,似乎就能说明问题。
如果 \(\gcd\) 是其他大于 \(1\) 的数 \(x\),那么则我们只能构造出 \(0,x,2x,3x,\dots\) 来,容易发现这也是这种情况下最优秀的
然后代码就比较好写了
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
sort(a+1,a+1+n);int minv=__gcd(a[1],a[2]);
if(n==1)goto ptans;
for(int i=3;i<=n;++i)minv=__gcd(minv,a[i]);
a[1]=0;
for(int i=2;i<=n;++i)a[i]=(i-1)*minv;
ptans:
for(int i=1;i<=n;++i){if(a[i]-(i-1)>=k){printf("%d\n",k+i-1-1);goto end;}
}printf("%d\n",k+n-1);
end:;
H. Sakurako's Test
给一个长为 \(n\) 的数组,给出一个数 \(x\),可以进行任意次下列操作
- 选择一个数 \(i(1\leq i\leq n)\),满足 \(a_i\geq x\)。进行赋值 \(a_i=a_i-x\)。
现在想要通过这一操作最小化 \(a\) 数组的中位数。对于每个数组,给 \(q\) 次询问,每次给出一个 \(x\),求出能最小化的中位数的值。(注意如果 \(n\) 是偶数,这里中位数指的是第 \(\frac{n+2}{2}\) 大的数)
\(1\leq \sum n,\sum q\leq 10^5,1\leq a_i\leq n,1\leq x\leq n\)
显然对于每个数,让它尽可能小是最优的,即 \(a_i\to a_i \bmod x\)。
那么我们现在要求转换后的数组的中位数,容易发现 \(x\) 的值很多,显然不能每次都暴力转化。
观察题目数据范围,\(1\leq a_i\leq n\),可以考虑将 \(a_i\) 存在桶 \(b_{a_i}\) 内,顺便求一下这个桶的前缀和 \(s\)。
因为取模后每个数 \(a_i\in[0,x-1]\),如果在 \([0,x-1]\) 内二分答案,每次求取模后小于等于 \(y\) 的数的个数,则可以在 \(n/x\) 的时间内求出答案(求法就是从头到尾枚举桶每个长度为 \(x\) 的区域(区域首尾相连),该区域内的答案即为该区域前 \(y\) 个桶的总和)。(懒得写数学公式了)
因为我们知道 $\sum_{x=1}^{n} n/x=n\sum_{x=1}^{n} 1/x\approx n\ln n $。所以总的复杂度 \(O(n\log^2n)\)
int n,q,ans[N],a[N],s[N],m,ques[N];
bool check(int lim,int k){int cnt=0;for(int i=0;i<=n;i+=k)cnt+=s[min(i+lim,n)]-(i==0?0:s[i-1]);return cnt>=n/2+1;
}
inline int solve(int k){int l=0,r=k-1,ans=0;while(l<=r){int mid=(l+r)>>1;if(check(mid,k))r=mid-1,ans=mid;else l=mid+1;}return ans;
}
int Test;
int main(){scanf("%d",&Test);while(Test--){scanf("%d%d",&n,&q);for(int i=1;i<=n;++i)a[i]=0,ans[i]=-1;for(int i=1;i<=n;++i){int x;scanf("%d",&x);++a[x];}for(int i=1;i<=n;++i)s[i]=s[i-1]+a[i];for(int i=1;i<=q;++i){scanf("%d",&ques[i]);if(ans[ques[i]]==-1)ans[ques[i]]=solve(ques[i]);}for(int i=1;i<=q;++i)printf("%d ",ans[ques[i]]);puts("");}return 0;
}
A. Sakurako's Exam
给你若干个数,这些数包括 \(a\) 个 \(1\) 和 \(b\) 个 \(2\),你能在这些数前面添加 \(+\) 或 \(-\) 使其和等于 \(0\),问能否做到。
容易发现我们可以只用关注 \(b\) 对 \(2\) 取模的结果,分类讨论即可
m%=2;
if(m){if(n%2==0&&n>=2)puts("YES");else puts("NO");
}else{if(n%2==0)puts("YES");else puts("NO");
}
B. Square or Not
一个漂亮的 \(01\) 矩阵是指数字 \(1\) 全在其外围而数字 \(0\) 全在其内部
现在将一个漂亮的 \(01\) 矩阵的字符串形式告诉你,问这个漂亮的 \(01\) 矩阵是否是一个正方形。对于一个大小为 \(r\times c\) 的矩阵,他的字符串 \(s\) 的第 \(((i-1)\times c+j)\) 个元素对应矩形的第 \(i\) 行第 \(j\) 列的元素。
做的时候还把题目看复杂了,以为要判断这个矩阵是不是漂亮的。
容易发现连续的 \(0\) 的数量 \(+2\) 就是这个矩阵的列数,除一下算出行数判断即可。
C. Longest Good Array
一个长为 \(n\) 的数列被认为是好的当且仅当
- 这个数列是递增的,即 \(a_{i-1}<a_i(2\leq i\leq n)\)。
- 两个连续数的差也是递增的,即 \(a_i-a_{i-1}<a_{i+1}-a_i(2\leq i<n)\)
现在一直一个好的数列的元素都在 \([l,r]\) 之间,求这个好的数列的最长长度 \((1\leq l\leq r\leq 10^9)\)
显然第一个元素为 \(l\) 是最优秀的,然后按照题意模拟求下一项即可,这是个二次等差数列,第 \(n\) 个数数量级 \(n^2\),足以通过此题。当然也可以求通项再做。
ll l,r;scanf("%lld%lld",&l,&r);
ll v=l,cnt=0;
while(v<=r){++cnt;v+=cnt;
}
printf("%lld\n",cnt);
D. Sakurako's Hobby
给定一个排列 \(p\),称一个数 \(j\) 能够从 \(i\) 到达当且仅当可以通过数次 \(i=p_i\) 操作让 \(i\) 等于 \(j\)。排列中的每个数为白色或黑色。定义函数 \(f(i)\) 表示从 \(i\) 可以到达的黑色的数的个数。现在求每个数的 \(f(i)\)
非常典,对 \(i\to p_i\) 连边,可以发现会形成若干个环。\(f(x)\) 就是 \(x\) 所在环中黑色数的个数。实现的话循环染色即可。
int n;scanf("%d",&n);
for(int i=1;i<=n;++i){scanf("%d",&p[i]);vis[i]=vb[i]=ans[i]=0;
}scanf("%s",s+1);
for(int i=1;i<=n;++i){int loc=i,cnt=0;while(!vis[loc]){vis[loc]=1;if(s[loc]=='0')++cnt;loc=p[loc];}loc=i;while(!vb[loc]){vb[loc]=1;ans[loc]=cnt;loc=p[loc];}
}
for(int i=1;i<=n;++i){printf("%d ",ans[i]);
}puts("");
E. Alternating String
如果一个长度为偶数的字符串奇数位的字符相同,偶数位上的字符相同,则称这个字符串为交错串。现在给你一个字符串,求最少通过下列多少次操作可以使字符串变为交错串。
- 删去字符串中的一个字符,字符串的长度 \(-1\)。注意:该操作最多只能进行一次。
- 选择一个字符将其修改为其他字符。
字符只能包括小写字母。
显然长度为偶数的字符串不进行操作1;长为奇数的字符串必须要进行一次操作1转化为偶数串,并且最先进行操作1不会使答案更劣。
先考虑偶数怎么做,偶数位和奇数位可以分开考虑,对于每种位,将其位上的字符变成这种位上出现次数最多的字符显然最优。开两个桶分别求 \(26\) 种字母在奇数位和偶数位上出现的次数即可。
在考虑奇数怎么做,枚举删除字符的位置,在像偶数做法一样求是 \(O(n^2|S|)\) 的。
可以考虑将删去字符之前的桶和之后的桶整合。考场做法是开一个 \(dp_{i,c,0/1}\) 表示前 \(2\times i\) 个字符中字符 \(c\) 在偶数或奇数位出现的次数(这就是一个前缀和)。
int ans=n,mv1=0,mv2=0;
for(int i=1;i<=n/2;++i){mv1=0,mv2=0;for(int j=0;j<26;++j){mv1=max(mv1,dp[i-1][j][0]+(dp[n/2][j][1]-dp[i-1][j][1]));mv2=max(mv2,dp[i-1][j][1]+(dp[n/2+1][j][0]-dp[i][j][0]));}ans=min(ans,n-mv1-mv2);mv1=0,mv2=0;for(int j=0;j<26;++j){mv1=max(mv1,dp[i][j][0]+(dp[n/2][j][1]-dp[i][j][1]));mv2=max(mv2,dp[i-1][j][1]+(dp[n/2+1][j][0]-dp[i][j][0]));}ans=min(ans,n-mv1-mv2);
}
mv1=0,mv2=0;
for(int j=0;j<26;++j){mv1=max(mv1,dp[n/2][j][0]);mv2=max(mv2,dp[n/2][j][1]);
}
ans=min(ans,n-mv1-mv2);
现在突然发现可以在枚举断点时就顺带维护前后的桶,码量小而且细节少(没想到复盘这些水题还有一些收获)
int ans=n;
for(int i=1;i<=n;++i)bot[s[i]-'a'][i&1]++;
for(int i=1;i<=n;++i){int mv1=0,mv2=0;--bot[s[i]-'a'][i&1];for(int j=0;j<26;++j){mv1=max(mv1,bot2[j][1]+bot[j][0]);mv2=max(mv2,bot2[j][0]+bot[j][1]);}ans=min(ans,n-mv1-mv2);++bot2[s[i]-'a'][i&1];
}
printf("%d\n",ans);
F. Sakurako's Box
给你 \(n\) 个数,求随机去两个数相乘,这个积的期望。
感觉比 E 思维难度小,而且没啥码量,就上床前顺带做了。
求出所有数对的积的和然后除以数对的个数即为期望。定义所有数的和为 \(s\),数对的积的和就是 \(\frac{1}{2}\times\sum a_i\times (s-a_i)\),数对的个数显然 \(\frac{n\times(n-1)}{2}\),求一下逆元即可。
scanf("%d",&n);
ll sum=0,ans=0;
for(int i=1;i<=n;++i){scanf("%lld",&a[i]);sum=(sum+a[i])%P;
}
for(int i=1;i<=n;++i)ans=(ans+a[i]*(sum-a[i]+P)%P)%P;
ans=ans*fpr(2)%P;
printf("%lld\n",ans*fpr(n)%P*fpr(n-1)%P*2%P);