cf1492e

· · 题解

注:本题解时间复杂度 O(36n^2+nm) 不保证正确。

正确数据在任意一行上改变不超过 2。所以设第一行为标准行,要求出改第一行的哪两个数。

如果直接枚举,需要 O(m^{2}),所以肯定不能直接枚举。需要观察一下可能改的数有什么规律。

改的数肯定是与第一行不同的数。若相同,改了反而会使差异更大。

设第一行与第 i 行的差异为 dif[i]。差异定义为 一个位置上的元素不同 的个数。于是对 dif 进行分类讨论。

$dif=4$:一定是要改变差异中的 $2$ 个。于是枚举哪两个。然后判断是否可行。由于只有4个,所以全部枚举也只需要 $C^{2}_{4}=6(\text{次})$ 即可。 $dif=3$:此种情况比较复杂。如果单纯改其中的 $2$ 个,会被这一组数据卡掉: ```cpp 3 3 0 1 2 3 4 5 6 7 8 ``` 这时,需要把 $1$ 改为 $4$,把 $2$ 改为 $8$。所以有可能改过的答案来自不同的 $2$ 行。 这时,需要枚举到底来自哪两行。同样,只会来自差异部分。 时间复杂度:此时并**不**是要枚举 $O(n^{2})$ 种情况。$dif[i]\leq3$,意味着至少要改成这一行中的一个差异。否则这一行差异不会变小,这样就无解了。所以只需要 $O(n)$ 找到其中一个 $dif[i]==3$ 的行,然后枚举所有的 $dif[j]=3$ 且 $i!=j$ 的 $j$。所以只需要 $O(n)$ 即可。 $dif\leq2$:此时无法判断是否改了。所以此信息对我们没用。如果仅有 $dif<=2$ 的行,直接输出第一行即可。 现在需要解决如何 $O(n)$ 求修改 $2$ 个数后的差异。枚举每一行,看改对(指原来错,现在对)有多少个,改错(原来对,现在错)有多少个,然后直接 $\text{总差异}\gets dif[i]-\text{改对}+\text{改错}$。具体见代码。 细节:因本题未单独限定 $n$ 与 $m$ 的范围,所以直接开二维数组不可取。于是模拟系统建二维数组的思路,$a[i][j]$ 表示为 $a[i\times m+j]$。这可以理解为简单的 有序数对hash。 总时间复杂度:$O(n^{2}+nm)$,常数为 $9\times 4=36$。 虽然时间复杂度较高,在 $n$ 很大的时候过不了,但是 codeforces 貌似没卡。 ```cpp #include<iostream> #include<cstdio> #include<cstring> using namespace std; int dif[1000005][7],tot[1000005],a[1000005],n,m; //第i行第j列:m*i+j。 void check(int b,int c,int d,int e){ //将位置b修改为c,将位置d修改为e。 if(b==d){ //如果b与d相同,说明只改了一个。此种情况已经枚举过了,故可直接返回。若不返回,则需将d,e设为-1。避免计算改对与改错时出错。 return; } int i,j,sum=0,dui,cuo; bool ykbb,ykbd; //ykbb表示b有没有改对一个数,ykbd表示d有没有改对一个数。 for(i=1;i<n;i++){ //对:之前没对&&现在对了。错:之前对了,现在错了。 dui=0; ykbb=(b>=0&&a[b]!=c); //一开始要是对的,才可能改错。 ykbd=(d>=0&&a[d]!=e); //判b,d为正的原因:若其为负,则说明该元素不改,详见main函数调用部分。 for(j=0;j<tot[i];j++){ //枚举差异。改对的元素只可能在这里面产生。 dui+=(b==dif[i][j]&&a[i*m+b]==c)+(d==dif[i][j]&&a[i*m+d]==e); //b与其相等才可能改对该元素。而b相等的情况下,值与c(现在的数)相同即为改对。 ykbb&=(b!=dif[i][j]); //若b与dif相等,则说明之前也是错的。最终答案取与即表示原先所有的差异位置都不与b相同。 ykbd&=(d!=dif[i][j]); } cuo=ykbb+ykbd; //二者相加表示改错了多少。 if(tot[i]-dui+cuo>2){ return; } } cout<<"Yes"<<endl; for(i=0;i<m;i++){ if(i==b){ //被改成b的部分输出c,改成d的部分输出e。否则未改,输出原值。 cout<<c<<" "; }else if(i==d){ cout<<e<<" "; }else{ cout<<a[i]<<" "; } } exit(0); //找到解了,直接结束程序。未结束说明该方案不可取。相当于return false; } int main(){ int i,j; bool ykb=0; cin>>n>>m; for(i=0;i<n;i++){ for(j=0;j<m;j++){ scanf("%d",a+m*i+j); } } for(i=1;i<n;i++){ dif[i][0]=m+1; dif[i][1]=m+1; dif[i][2]=m+1; dif[i][3]=m+1; for(j=0;j<m;j++){ if(a[j]!=a[m*i+j]){ dif[i][tot[i]++]=j; } } if(tot[i]>=5) cout<<"No"; return 0; } } for(i=0;i<n;i++){ if(tot[i]==4){ check(dif[i][0],a[m*i+dif[i][0]],dif[i][1],a[m*i+dif[i][1]]); check(dif[i][0],a[m*i+dif[i][0]],dif[i][2],a[m*i+dif[i][2]]); check(dif[i][0],a[m*i+dif[i][0]],dif[i][3],a[m*i+dif[i][3]]); check(dif[i][1],a[m*i+dif[i][1]],dif[i][2],a[m*i+dif[i][2]]); check(dif[i][1],a[m*i+dif[i][1]],dif[i][3],a[m*i+dif[i][3]]); check(dif[i][2],a[m*i+dif[i][2]],dif[i][3],a[m*i+dif[i][3]]); cout<<"No"; return 0; } } for(i=1;i<n;i++){ if(tot[i]==3){ check(dif[i][0],a[m*i+dif[i][0]],-3,-3); //现在只要改一个元素,那么第二个修改值定为原数组中不存在的下标,就相当于没改。 check(dif[i][1],a[m*i+dif[i][1]],-3,-3); check(dif[i][2],a[m*i+dif[i][2]],-3,-3); for(j=1;j<n;j++){ if(i!=j){ check(dif[i][0],a[m*i+dif[i][0]],dif[j][1],a[m*j+dif[j][1]]); check(dif[i][0],a[m*i+dif[i][0]],dif[j][2],a[m*j+dif[j][2]]); check(dif[i][1],a[m*i+dif[i][1]],dif[j][0],a[m*j+dif[j][0]]); check(dif[i][1],a[m*i+dif[i][1]],dif[j][2],a[m*j+dif[j][2]]); check(dif[i][2],a[m*i+dif[i][2]],dif[j][0],a[m*j+dif[j][0]]); check(dif[i][2],a[m*i+dif[i][2]],dif[j][1],a[m*j+dif[j][1]]); } } cout<<"No"; return 0; } } cout<<"Yes"<<endl; //说明未出现>=3的情况,若出现,早就返回了。 for(i=0;i<m;i++){ cout<<a[i]<<" "; } return 0; } ``` 附一组可以卡掉本算法的数据(生成在 ```1.in```)(*windows7* 下跑了 $81.76$s): ```cpp #include<iostream> using namespace std; int main(){ freopen("1.in","w",stdout); int i; cout<<"83333 3"<<endl; for(i=0;i<40000;i++){ cout<<"0 0 0"<<endl; } for(i=0;i<43333;i++){ cout<<i*3<<" "<<i*3+1<<" "<<i*3+2<<endl; } return 0; } ```