题意:
给一个 n×m 的矩阵,你想要把它还原成如下的矩阵
你有两种操作:
• 选择一个数,修改它的值。
• 选择一列,每个元素上移一位,第一行的元素移到第 n 行。
求还原的最小操作次数。
数据范围:
1≤n,m≤2⋅10^5
1≤n⋅m≤2⋅10^5
思路:
对于任意一行,我们可以发现,无论是先进行单点修改,后进行整列上移,还是先进行整列上移,后进行单点修改。都是没有影响的,两种操作是互相独立的。所以我们可以设定samej,i表示第j列上移i个单位后,有多少个数是不需要改变的,这时我们可以得到对于任意一列,操作次数是n-samej,i+i,因为n-samej,i是需要改变的操作次数,i是上移的次数,所以对于任意一列,最优答案就为min(0≤i<n)(n-samej,i+i)。所以最终整体答案为所有列最优答案的求和。这时我们就只需要求出same即可,在判断ai,j是否属于第j列的数字时,有三个条件:j≤ai,j , ai,j≤m*n , (ai,j-j) % m = 0.满足这三个条件,就可以说明ai,j是第j列的数字。然后我们还需要判断ai,j应该在第几行,我们设ai,j应该在第k行。通过还原后的矩阵得到公式k=(ai,j-j)/m+1。最后判断当前列和第k列的位置,如果k≤i,就same[i-k]++,如果k>i,就same[i-k+n]++.最后通过最上面的公式求得答案即可。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int mod=1e9+7;
int m,n,same[maxn],ans;
vector<int> a[maxn]; //开vector容器存矩阵
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
a[i].push_back(0); //保证下标为1
for(int j=1;j<=m;j++)
{
int t;cin>>t;
a[i].push_back(t);
}
}
for(int j=1;j<=m;j++)
{
int cnt=INT_MAX;
for(int i=0;i<n;i++)
same[i]=0;
for(int i=1;i<=n;i++)
{
if(a[i][j]>=j&&a[i][j]<=(m*n)&&(a[i][j]-j)%m==0)
{
int k=(a[i][j]-j)/m+1;
if(i>=k)
same[i-k]++;
else
same[i-k+n]++;
}
}
for(int i=0;i<n;i++)
cnt=min(cnt,i+n-same[i]);
ans+=cnt;
}
cout<<ans;
return 0;
}