一开始以为是个模式匹配题,其实要先推出性质,就很好做了
/* 由于每列都具有独立性,所以一列一列计算答案 问题转化成给定一个数组a,要对其进行最小次数的循环上移或修改,最后匹配数组b 观察b数组的性质,我们发现第j列的数组b[j][] = {j+(i-1)m},为了方便处理,我们把第j列的a,b数组统统-j+m 处理后的b[j][i]=im,我们只要考虑a数组里是m倍数的数即可,剩下的数不管怎么移动永远配不上 我们再来考虑剩下的数,考虑a[i]=km k==i,那么a[i]属于不用移动就能配上 k<i,那么a[i]上移i-k能配上 k>i,那么a[i]上移i+n-k能配上 开个桶cnt[i]来记录上移i格配上的数字个数,找到消耗最小的上移策略即可 */ #include<bits/stdc++.h> using namespace std; #define N 200005 vector<int>tmp[N],a[N]; int sum,n,m; int cnt[N]; int solve(int col,vector<int>a){ int res=0; cnt[0]=0; for(int i=1;i<=n;i++)a[i]+=-col+m,cnt[i]=0;//初始化 for(int i=1;i<=n;i++){ if(a[i]%m!=0 || a[i]/m>n)res++; else { int k=a[i]/m; if(k==i)cnt[0]++; else if(k<i)cnt[i-k]++; else cnt[i+n-k]++; } } int Min=0x3f3f3f3f,base=n-res; for(int i=0;i<=n;i++) Min=min(Min,i+base-cnt[i]); return Min+res; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ tmp[i].push_back(0); for(int j=1;j<=m;j++){ int x;scanf("%d",&x); tmp[i].push_back(x); } } for(int j=1;j<=m;j++){ a[j].push_back(0); for(int i=1;i<=n;i++) a[j].push_back(tmp[i][j]); } for(int j=1;j<=m;j++) sum+=solve(j,a[j]); cout<<sum<<' '; }