上下交换:会使某两行中感兴趣的摊点数改变
左右交换:会使某两列中感兴趣的摊点数改变
可以发现,行和列的操作是互相独立的
所以我们可以把问题分成两个子问题:
1.最少几次操作可以使所有行中感兴趣的摊点数数量相等
2.最少几次操作可以使所有列中感兴趣的摊点数数量相等
下面讨论对行进行操作(列类似):
假设有n行,每行中感兴趣的摊点数量是a[i]。我们每次可以对相邻的两行进行操作。
操作是a[i]+1,a[i+1]-1或者a[i]-1,a[i+1]+1。
如果不考虑首尾可以交换,该问题就是经典的均分纸牌问题。
若a[i]的和S是n的倍数,则有解,否则无解。
讨论有解的情况:就是答案,s[i]为a的前缀和。
该式子表示一个前缀需要跟后面交换|i*(S/n)-s[i]|次,所有前缀加起来就是答案。
但是本问题可以首尾交换,就变成了环形均分纸牌
可以发现,最少次数一定存在某两行不进行交换,即将环破成链。
如果暴力去枚举断点,再去计算每种情况的答案,时间复杂度会变为O()。
我们观察一下:
当以k为断点时,数组变为了
a[k+1] |1*(S/n)+s[k+1]-s[k]|
···
a[n] |(n-k)*(S/n)+s[n]-s[k]|
a[1] |(n-k+1)*(S/n)+s[1]+s[n]-s[k]|
···
a[k] |n*(S/n)+s[k]+s[n]-s[k]|
左边是a数组,右边是以k为断点时每个前缀的答案。
我们需要将右边相加。这样看起来不好操作。
如果在初始时我们将每个a[i]减去一个(S/n),让最终的a[i]全部变为0,需要的步数和现在是一样的。
则上式的(S/n)变成了0,s[n]也变成了0:
a[k+1] |s[k+1]-s[k]|
···
a[n] |s[n]-s[k]|
a[1] |s[1]-s[k]|
···
a[k] |s[k]-s[k]|
现在就是要我们求每个s到s[k]的距离之和,这不就是之前的货舱选址问题吗。
s数组是每个商店的位置,s[k]是货舱的位置。问题解决完毕
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
ll query(int a[],int n){
static ll s[N];
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
if(s[n]%n)return -1;
for(int i=1;i<=n;i++)s[i]-=i*s[n]/n;
sort(s+1,s+1+n);
ll ans=0;
for(int i=1;i<=(n+1)/2;i++)ans+=s[n-i+1]-s[i];
return ans;
}
int rows[N],cols[N];
int n,m,t;
int main(){
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=t;i++){
int x,y;
scanf("%d%d",&x,&y);
rows[x]++,cols[y]++;
}
ll a1=query(rows,n),a2=query(cols,m);
if(a1!=-1&&a2!=-1)printf("both %lld",a1+a2);
else if(a1==-1&&a2==-1)printf("impossible");
else if(a1==-1)printf("column %lld",a2);
else printf("row %lld",a1);
return 0;
}