Codeforces Round 991 (Div. 3) 复盘
CF991Div3 复盘
人生中第一次 AK codeforces,还是有点小激动,然而赛后发现是超级水的一场(应该最后AK了600多个人),突然感觉又索然无味了。不过还是简单记录一下吧。
# | 提交者 | = | Penalty | * | A | B | C | D | E | F | G |
---|---|---|---|---|---|---|---|---|---|---|---|
1 (58) | BaSEc1d | 7 | 218 | +00:04 | +00:09 | +00:17 | +00:23 | +00:37 | +00:53 | +01:15 | |
Accepted Tried | 17655 17894 | 9824 11914 | 7745 9579 | 5146 5793 | 3026 3223 | 1482 1842 | 375 965 |
A. Line Breaks
纯签到,依据题意模拟即可
B. Transfusion
给一个长为 \(n\) 的序列 \(a\),每次选择一个 \(2\sim n-1\) 的下标,让 \(a_{i-1}:=a_{i-1}-1\) 且 \(a_{i+1}:=a_{i+1}+1\) 或者 \(a_{i-1}:=a_{i-1}+1\) 且 \(a_{i+1}:=a_{i+1}-1\)。问能否执行若干次操作,使得这个序列最后所有数相等。同时操作过程中这个序列不得出现负值。
发现奇数位置的数和偶数位置的数互不影响。显然最后奇数位置上的数要变成他们的平均数,偶数位置上的数也要变成他们的平均数。如果这两个平均数不相等或者是小数肯定是不行的。
现在我们只需要证明一定可以存在一种操作方法使得他们能变成平均数。
这个显然是可以的,只需要考虑非常极端的一种操作,假设我们要让第一个数变成平均数,可以把后面所有的数加到这个数上面(后面的数变成 \(0\)),然后再把这个数减到平均数。接着剩余的值转移到了下一个位置,再把下一个位置上的数减到平均数,依次进行这样的操作就好了。
while(Test--){
scanf("%d",&n);ll s1=0,s2=0;
for(int i=1;i<=n;++i){
scanf("%d",a+i);
if(i&1)s1+=a[i];
else s2+=a[i];
}
ll n1=(n+1)/2,n2=n-n1;
if(s1%n1==0&&s2%n2==0&&s1/n1==s2/n2)puts("YES");
else puts("NO");
}
C. Uninteresting Number
给你一个长度不超过 \(10^5\) 的数字 \(n\) 。
你可以多次进行以下运算:选择其中一位数字,将其平方,然后用运算结果替换原来的数字。结果必须是一位数字(也就是说,如果您选择数字 \(x\) ,那么 \(x^2\) 的值必须小于 \(10\) )。
通过这些运算,有可能得到一个能被 \(9\) 整除的数吗?
显然我们只需要执行 \(2\to 4,3\to 9\) 的操作,让所有数字的和为 \(9\) 的倍数即可。
我们只关心 \(2\) 和 \(3\) 的个数,如果 \(2\) 的个数多余 \(9\) 个或者 \(3\) 的个数多于 \(3\) 个都是没有意义的,然后枚举改变几个 \(2\) 几个 \(3\) 就行了。
scanf("%s",s+1);n=strlen(s+1);
int c2=0,c3=0;ll sum=0;
for(int i=1;i<=n;++i){
if(s[i]=='2')++c2;
else if(s[i]=='3')++c3;
sum+=s[i]-'0';
}
bool flag=1;
for(int i=0;i<=min(c2,20)&&flag;++i){
for(int j=0;j<=min(c3,20)&&flag;++j){
if((sum+2*i+6*j)%9==0){
flag=0;
}
}
}
if(flag)puts("NO");
else puts("YES");
D. Digital string maximization
给你一个由 \(0\) 至 \(9\) 的数字组成的字符串 \(s\) 。在一次运算中,您可以选取该字符串中除 \(0\) 或最左边数字之外的任意一个数字,将其减少 \(1\) ,然后将其与左边的数字对调。
例如,从字符串 \(1023\) 中进行一次运算,可以得到 \(1103\) 或 \(1022\) 。
找出任意多次运算后可以得到的词序最大字符串。
贪心,显然要让最高位更优,如果同时有多个方案让最高位更优,选减少次数更少的。具体实现见代码
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;++i)a[i]=s[i]-'0';
for(int i=1;i<=n;++i){
int mxl=i;
for(int j=i+1;j<=min(n,i+9);++j){
if(a[j]-(j-i)>a[mxl]-(mxl-i))
mxl=j;
}
for(int j=mxl;j>=i+1;--j){
--a[j];
swap(a[j],a[j-1]);
}
}
for(int i=1;i<=n;++i)
printf("%d",a[i]);
puts("");
E. Three Strings
您将得到三个字符串: \(a\) 、 \(b\) 和 \(c\) ,由小写拉丁字母组成。字符串 \(c\) 是通过以下方法得到的:
- 每一步都会随机选择字符串 \(a\) 或字符串 \(b\) ,然后将所选字符串的第一个字符移除并附加到字符串 \(c\) 的末尾,直到其中一个字符串用完为止。之后,将非空字符串的剩余字符添加到 \(c\) 的末尾。
- 然后,随机更改字符串 \(c\) 中的一定数量字符。
例如,从字符串 \(a=\color{red}{\text{abra}}\) 和 \(b=\color{blue}{\text{cada}}\) 中,在不替换字符的情况下,可以得到字符串 \(\color{blue}{\text{ca}}\color{red}{\text{ab}}\color{blue}{\text{d}}\color{red}{\text{ra}}\color{blue}{\text{a}}\) 、 \(\color{red}{\text{abra}}\color{blue}{\text{cada}}\) 、 \(\color{red}{\text{a}}\color{blue}{\text{cada}}\color{red}{\text{bra}}\) 。
求字符串 \(c\) 中最少可以替换的字符数。
简直就是板子的一个 \(O(n^2)\) dp。
\(dp_{i,j}\) 表示取 \(i\) 位字符串 \(a\),取 \(j\) 位字符串 \(b\) 组成的字符串最少可替换的字符数。
做这题的时候非常唐,写成 scanf("%s%s%S",a+1,b+1,c+1)
了,导致 \(c\) 是空的错了,然后对自己的转移没自信写了个记忆化搜索,后面才发现是自己输入错了。
int Test;
char a[N],b[N],c[N];int n,m,dp[N][N];
inline int dfsp(int i,int j){
if(dp[i][j]!=-1)return dp[i][j];
int tmp=INF;
if(i)tmp=min(tmp,dfsp(i-1,j)+(a[i]!=c[i+j]));
if(j)tmp=min(tmp,dfsp(i,j-1)+(b[j]!=c[i+j]));
return dp[i][j]=tmp;
}
int main(){
scanf("%d",&Test);
while(Test--){
scanf("%s%s%s",a+1,b+1,c+1);
n=strlen(a+1),m=strlen(b+1);
for(int i=0;i<=n;++i)
for(int j=0;j<=m;++j)
dp[i][j]=-1;
dp[0][0]=0;
printf("%d\n",dfsp(n,m));
}
return 0;
}
F. Maximum modulo equality
给你一个长度为 \(n\) 的数组 \(a\) 和 \(q\) 查询 \(l\) , \(r\) .
请为每个查询找出最大可能的 \(m\) ,使得所有元素 \(a_l\) , \(a_{l+1}\) , ..., \(a_r\) 都等于 \(m\) 的模。换句话说, \(a_l \bmod m = a_{l+1} \bmod m = \dots = a_r \bmod m\) ,其中 \(a \bmod b\) - 是 \(a\) 除以 \(b\) 的余数。特别是,当 \(m\) 可以是无限大时,请打印 \(0\) 。
应该是全场唯一还有点意思的题了,不过也非常的套路其实。
对于长度为 \(1\) 的区间,答案显然就是 \(0\),对于长度为 \(2\) 的区间 \([l,l+1]\),\(a_{l}\bmod m=a_{l+1}\bmod m\),则有 \(|a_{l}-a_{l+1}|\bmod m=0\),显然 \(m\) 最大应该取 \(|a_{l}-a_{l+1}|\),然后 \(m\) 取 \(|a_l-a_{l+1}|\) 的因子是可以满足柿子成立的。
于是合并区间的时候,答案就是多段区间答案的最大公因数。因为 \(\gcd(0,x)=x\),所以直接写是没问题的。
std 是 st 表,但是线段树复杂度也是能够接受的。
const int N=200005;
int Test;
int n,m,a[N];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
int val[N<<2];
inline void pushup(int p){val[p]=__gcd(val[ls],val[rs]);}
void build(int p,int l,int r){
if(l==r){
val[p]=max(a[l+1],a[l])-min(a[l+1],a[l]);
return;
}
build(ls,l,mid);build(rs,mid+1,r);
pushup(p);
}
int query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return val[p];
int tmp=0;
if(L<=mid)tmp=__gcd(tmp,query(ls,l,mid,L,R));
if(R>mid)tmp=__gcd(tmp,query(rs,mid+1,r,L,R));
return tmp;
}
int main(){
scanf("%d",&Test);
while(Test--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
if(n>1)build(1,1,n-1);
while(m--){
int l,r;scanf("%d%d",&l,&r);
if(l==r){printf("0 ");continue;}
int g=query(1,1,n-1,l,r-1);
printf("%d ",g);
}puts("");
}
return 0;
}
G. Tree Destruction
给定一棵有 \(n\) 个顶点的树 \(^{\text{∗}}\) 。您可以选择两个顶点 \(a\) 和 \(b\) 一次,然后删除从 \(a\) 到 \(b\) 的路径上的所有顶点,包括顶点本身。如果您选择 \(a=b\) ,则只会移除一个顶点。
你的任务是找出从树中移除一条路径后所能形成的最大连通组件数 \(^{\text{†}}\) 。
\(^{\text{∗}}\) 树是一个没有循环的连通图。
\(^{\text{†}}\) 连通部分是这样一个顶点集合:从任意顶点到集合中的任意其他顶点都有一条沿边的路径(并且不可能到达不属于这个集合的顶点)。
假设树上点 \(u\) 的度数是 \(deg_u\)。
现在假设我们删除了一条链,链中间的点(不算两端)对答案的贡献是 \(deg_u-2\),链两端的点对答案的贡献是 \(deg_u-1\)。那么我们给树上每个点赋权值 \(val_u=deg_u-2\),最后的答案就是找一条 \(\sum val\) 最大的链,再加上 \(2\)。这个树形 DP 维护一下当前点 \(u\) 到已遍历子树的最长链,合并下一个子树的时候统计一下即可。
这个算法有一个小问题就是如果删除的不是链而是一个点,就有问题,所以特判一下删除点的情况即可。如果删除点 \(u\),此时答案就是 \(deg_u\)。(好在赛时多想了一会发现了这一点,不然就要吃罚时了)
const int N=200005,INF=0x3f3f3f3f;
int Test;
int n,a[N],ind[N],val[N],ans=-INF,fir[N],sec[N];
vector<int>tar[N];
void dfs(int u,int f){
fir[u]=val[u];
for(auto v:tar[u]){
if(v==f)continue;
dfs(v,u);
ans=max(ans,fir[u]+fir[v]);
fir[u]=max(fir[u],val[u]+fir[v]);
}
}
int main(){
scanf("%d",&Test);
while(Test--){
scanf("%d",&n);ans=-INF;
for(int i=1;i<=n;++i)tar[i].clear(),ind[i]=0;
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
tar[u].push_back(v);
tar[v].push_back(u);
++ind[u],++ind[v];
}
for(int i=1;i<=n;++i)val[i]=ind[i]-2;
dfs(1,0);ans+=2;
for(int i=1;i<=n;++i){
ans=max(ans,ind[i]);
}
printf("%d\n",ans);
}
return 0;
}
嗯想在后面说点什么,但又不知道怎么说,那就不说了吧。
本文作者:BigSmall_En
本文链接:https://www.cnblogs.com/BigSmall-En/p/18600881
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效