思维——cf1294E

一开始以为是个模式匹配题,其实要先推出性质,就很好做了

/*
由于每列都具有独立性,所以一列一列计算答案
问题转化成给定一个数组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<<'
';
} 
原文地址:https://www.cnblogs.com/zsben991126/p/12260106.html