Codeforces 1294E Obtain a Permutation

题目大意

给定一个\(N\times M\) 的矩阵 \((1 \leq N,M \leq 2 \times 10^5 , N \times M \leq 2 \times 10^5)\)

有两种操作,
操作一:选取任意一个元素,将它改变成任意值
操作二:选取某一列,将这一列的所有元素循环上移一格,如下图将第一列循环上移一格

要求用最少的操作步数,使得矩阵变成如下形式

输出最少的操作步数。

题解

我们用输入的矩阵减去最终的矩阵,可以得到一个增量矩阵,对应着原矩阵的每个元素还要变化多少,才能得到最终的矩阵。

容易发现,列与列之间互不干扰,每一列都可以独立计算出这一列的最少操作步数,所有列的最少操作步数相加后即得答案。

举个例子
不妨令\(N=4,M=3\),
则第一列一定是\(1,4,7,10\)
依次使用操作二,
\(1,4,7,10\) 移动0次
\(10,1,4,7\) 移动1次
\(7,10,1,4\) 移动2次
\(4,7,10,1\) 移动3次

每一列的增量矩阵依次变为
\(0,0,0,0\)
\(9,-3,-3,-3\)
\(6,6,-6,-6\)
\(3,3,3,-9\)
可以发现,增量矩阵中的每个元素一定是\(M\)的倍数,否则不符合要求,只能通过操作一去修改

把增量矩阵的每一个元素除以\(M\),得
\(0,0,0,0\)
\(3,-1,-1,-1\)
\(2,2,-2,-2\)
\(1,1,1,-3\)
得到的这个东西很有规律。

可以发现,如果我们把当前列的第\(i\)个元素移动到第一个位置上,需要移动\(i\)次,且这一列的增量矩阵除以\(M\)后只能有上面给出的两个数。

比如\(2,2,-2,-2\),需要移动\(2\)次,且前\(2\)个元素只能是\(2\),后两个元素只能是\(-2\)

再比如\(1,1,1,-3\) ,需要移动\(3\)次,且前\(3\)个元素只能是\(1\),后两个元素只能是\(-3\)

只要维护一个Cnt数组,对于每一列扫一遍它的增量矩阵,即可算出至少需要通过操作一改变几个元素才能再使用\(i\)次操作二得到最终的矩阵。

时间复杂度\(O(MN)\)

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

vector<int> Data[200010];
int Cnt[400010];
int N,M;

template<typename elemType>
inline void Read(elemType &T){
    elemType X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    T=(w?-X:X);
}

inline int Calc(int pos){
    for(register int i=0;i<N;++i){
        int x=Data[pos][i];
        if(x%M!=0) continue;
        x/=M;
        if(x>N-1 || x<-(N-1)) continue;
        if(x<=0 && i>=-x) ++Cnt[x+N-1];
        else if(x>0 && i<N-x) ++Cnt[x+N-1];
    }
    int Res=2147483647;
    for(register int i=0;i<=N-1;++i){
        int temp=(N-i)-Cnt[N-i-1]+i-Cnt[N-i+N-1]+i;
        Res=min(Res,temp);
    }
    for(register int i=0;i<2*N-1;++i)
        Cnt[i]=0;
    return Res;
}

int main(){
    Read(N);Read(M);
    for(register int i=1;i<=N;++i){
        for(register int j=1;j<=M;++j){
            int x;Read(x);
            Data[j].push_back(x-((i-1)*M+j));
        }
    }
    int Ans=0;
    for(register int i=1;i<=M;++i)
        Ans+=Calc(i);
    cout<<Ans<<endl;

    return 0;
}
posted @ 2020-01-26 16:11  AE酱  阅读(147)  评论(0编辑  收藏  举报