排序3 七夕祭

上下交换:会使某两行中感兴趣的摊点数改变
左右交换:会使某两列中感兴趣的摊点数改变
可以发现,行和列的操作是互相独立的
所以我们可以把问题分成两个子问题:
1.最少几次操作可以使所有行中感兴趣的摊点数数量相等
2.最少几次操作可以使所有列中感兴趣的摊点数数量相等
下面讨论对行进行操作(列类似):
假设有n行,每行中感兴趣的摊点数量是a[i]。我们每次可以对相邻的两行进行操作。
操作是a[i]+1,a[i+1]-1或者a[i]-1,a[i+1]+1。
如果不考虑首尾可以交换,该问题就是经典的均分纸牌问题。
若a[i]的和S是n的倍数,则有解,否则无解。
讨论有解的情况:
\sum_{i=1}^{n}|i*S/n-s[i]|就是答案,s[i]为a的前缀和。
该式子表示一个前缀需要跟后面交换|i*(S/n)-s[i]|次,所有前缀加起来就是答案。
但是本问题可以首尾交换,就变成了环形均分纸牌
可以发现,最少次数一定存在某两行不进行交换,即将环破成链。
如果暴力去枚举断点,再去计算每种情况的答案,时间复杂度会变为O(n^{2})。
我们观察一下:
当以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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值