CF1885 前三题题解
2023-07-30 18:55:15
水平有限,等我有时间看懂了后面的题再写后面的吧。
写完三题太困了直接睡了(AT和CF一起打真的非常累啊啊啊)。
A
题意:给你一个排列找使其 \(\forall i \in [1,n],a_i\ne i\) 的最小交换次数。
发现偶数个相同就直接两两交换答案除二,奇数个相同就向下取整交换然后再找随便一个交换即可。
int T,n,a[N],ans;
int main(){
T=read();
while(T--){
n=read(),ans=0;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)if(a[i]==i)ans++;
printf("%d\n",(ans+1)>>1);
}
}
B
一开始还在想是不是有什么根号做法,然后看到这个数据范围觉得这至少得开五次根的算法才能过啊,那前面四次是不是就得分类讨论了。
当 \(n\) 是奇数时,因为如果长度超过 \(2\) 那肯定就出现了偶数,奇数显然不能整除偶数,所以此时只有 \(1\)。
考虑当长度为 \(2\) 时,假设存在 \(x,x+1\) 使得 \(n|x,n|x+1\),因为 \(x,x+1\) 中至少有一个是偶数,所以选他们肯定不如直接选 \(1,2\) 来得更优。
考虑当长度为 \(3\) 时,假设存在 \(x,x+1,x+2\) 使得 \(n|x,n|x+1,n|x+2\),因为 \(x,x+1,x+2\) 中至少有一个是偶数,并且至少有一个是 \(3\) 的倍数,所以选他们肯定不如直接选 \(1,2,3\) 来得更优。
算到这里我突然感觉到答案就很显然了,根本不需要根号做法,直接从 \(1\) 开始枚举即可,因为假设后面也出现了长度为 \(len\) 的串,那么显然这个数可以同时被 \(1~len\) 的所有数整除。
其实这个结论很好想到,但是愚蠢的我被样例的解释中的 \([327869,327871]\) 狠狠地迷惑到了。
\(Code\)
ll T,n,ans;
int main(){
T=read();
while(T--){
n=read();
for(ans=1;ans<=10000006;ans++)if(n%ans!=0)break;
printf("%lld\n",ans-1);
}
}
C1
提供一个只能过 \(C1\)的构造,因为当序列都是非负数时,我们只需要用类似前缀和的方式就能使序列单调不降;都是非正数时,我们只需要让其用类似于后缀和的方式单调不降。
那如果两个都有呢?很简单,把它们都加成正数或者负数即可。
把正数变到 \(20\) 以上最多 \(5\) 次,加给负数最多 \(10\) 次,前缀加最多 \(19\) 次,一共不超过 \(34\) 次,可以通过 \(C1\)。
\(Code\)
int T,n,a[22],from[55],to[55];
int main(){
T=read();
while(T--){
n=read();
int fcnt=0,zcnt=0,fmin=0,fi,zi,zmax=0,p=0;
for(int i=1;i<=n;i++){
a[i]=read();
if(a[i]<0){
fcnt++;
if(a[i]<fmin)fmin=a[i],fi=i;
}else if(a[i]>0){
zcnt++;
if(a[i]>zmax)zmax=a[i],zi=i;
}
}
if(!fcnt&&!zcnt)puts("0");
else{
if(zcnt>fcnt){
while(zmax<=20)from[++p]=zi,to[p]=zi,zmax*=2;
for(int i=1;i<=n;i++)if(a[i]<0)from[++p]=zi,to[p]=i;
for(int i=1;i<n;i++){
from[++p]=i,to[p]=i+1;
}
}else{
while(fmin>=-20)from[++p]=fi,to[p]=fi,fmin*=2;
for(int i=1;i<=n;i++)if(a[i]>0)from[++p]=fi,to[p]=i;
for(int i=n-1;i;i--){
from[++p]=i+1,to[p]=i;
}
}
printf("%d\n",p);
for(int i=1;i<=p;i++)printf("%d %d\n",to[i],from[i]);
}
}
}

浙公网安备 33010602011771号