DestinHistoire

 

BZOJ-3032 七夕祭(前缀和+中位数)

题目描述

  有 \(N\times M\) 个点,其中 \(T\) 个点是关键点,第 \(i\) 个关键点坐标为 \((x_i,y_i)\),每次可以交换相邻两个点,每一行或每一列的第一个位置和最后一个位置也算作相邻。

  目标是通过交换一些相邻点,使得每行每列的关键点个数相同。如果能满足全部两个要求,输出 both;如果只能使每行关键点数量相同,输出 row;如果只能使每列关键点数量相同,输出 column;如果均不能满足,输出 impossible;如果输出的字符串不是 impossible,再输出最小交换次数。

  数据范围:\(1\leq N,M\leq 10^5,0\leq T\leq \min(N\times M,10^5)\)。

分析

  可以发现,交换左右两个点只会改变某两列的关键点数,不会改变每行中的关键点数。同理,交换上下两个相邻的点只会改变某两行中的关键点数,不会改变每列中的关键点数。

  所以我们可以分成两个相互独立的部分计算:

  \(1.\) 通过最少次数的左右交换使每列中关键点数相同。

  \(2.\) 通过最少次数的上下交换使每行中关键点数相同。

  统计出在初始情况下,每列中关键点总数,记为 \(C[1]\) ~ \(C[M]\)。如果 \(M\nmid T\),则不可能达到要求,如果 \(M\mid T\),则目标就是让每列中有 \(T/M\) 个关键点。

  联想到 均分纸牌 问题,当所有人手中的纸牌总数 \(T\) 能被 \(M\) 整除时有解,在有解时,我们可以先考虑第一个人:

  \(1.\) 若 \(C[1]>T/M\),则第一个人需要给第二个人 \(C[1]-T/M\) 张纸牌,即把 \(C[2]\) 加上 \(C[1]-T/M\)。

  \(2.\) 若 \(C[1]<T/M\),则第一个人需要从第二个人手中拿 \(T/M-C[1]\) 张纸牌,即把 \(C[2]\) 减去 \(T/M-C[1]\)。

  按照同样的方法依次考虑第 \(2\) ~ \(M\) 个人。即使在某个时刻有某个 \(C[i]\) 被减为负数也没有关系,因为接下来 \(C[i]\) 就会从 \(C[i+1]\) 处拿牌,在实际中可以认为 \(C[i]\) 从 \(C[i+1]\) 处拿牌发生在 \(C[i-1]\) 从 \(C[i]\) 处拿牌之前。按照这种方法,经过计算,达到目标所需要的最少步数就是:

\[\sum_{i=1}^{M}\Big|i\times \frac{T}{M}-S[i]\Big|,G 是 C 的前缀和 \]

  其含义是每个前缀最初共持有 \(G[i]\) 张纸牌,最终会持有 \(i\times T/M\) 张纸牌,多退少补,会与后边的人发生二者之差的绝对值张纸牌的交换。

  如果我们设 \(A[i]=C[i]-T/M\),即一开始就让每个人手中的纸牌数都减掉 \(T/M\),并且最终让每个人手里都恰好有 \(0\) 张纸牌,答案显然不变,就是:

\[\sum_{i=1}^{M}|S[i]|,S是A的前缀和 \]

  回到本题,如果不考虑第 \(1\) 列与最后一列也是相邻的这一条件,那么刚才提到的本题中的第 \(1\) 个问题与均分纸牌问题是等价的。若问题有解,一定存在一种适当的顺序,使得每一步传递纸牌的操作可以转化为交换一对左右相邻的摊点(两个点中有一个是关键点)。

  若第 \(1\) 列与最后一列相邻,则问题等价于一个 环形均分纸牌。仔细思考可以发现,一定存在一种最优解的方案,环上某两个相邻的人之间没有发生纸牌交换操作。于是有一种朴素的做法是,枚举这个没有发生交换的位置,把环断开看成一行,转化为一般的 均分纸牌 问题进行计算。

  首先,一般的 均分纸牌 问题就相当于在第 \(M\) 个人与第 \(1\) 个人之间把环断开,此时这 \(M\) 个人写成一行,其持有的纸牌数、前缀和分别是:

\[\begin{aligned}A[1]~~~~S[1]\\A[2]~~~~S[2]\\\cdots~~~~~\cdots~\\A[M]~~~~S[M]\end{aligned} \]

  如果在第 \(k\) 个人之后把环断开写成一行,这 \(M\) 个人持有的纸牌数、前缀和分别是:

\[\begin{aligned}&A[k+1]\quad S[k+1]-S[k]\\&A[k+2]\quad S[k+2]-S[k]\\&\quad\cdots\quad\quad\quad\quad\quad\cdots\quad\quad\\&A[M]\quad \quad \quad S[M]-S[k]\\&A[1]\quad\quad S[1]+S[M]-S[k]\\&\quad\cdots\quad\quad\quad\quad\quad\cdots\quad\quad\\&A[k]\quad\quad S[k]+S[M]-S[k]\end{aligned} \]

  此处 \(A\) 是减去最终每个人手里纸牌数 \(T/M\) 之后的数组,\(A\) 数组均分之后每个人手里都有 \(0\) 张纸牌,所以 \(S[M]=0\)。也就是说,从第 \(k\) 个人把环断开写成一行,前缀和数组的变化是每个位置都减掉 \(S[k]\)。

  根据上面推导的公式,所需最少步数为:

\[\sum_{i=1}^{M}|S[i]-S[k]| \]

  不需要枚举 \(k\),把 \(S\) 从小到大排序,取中位数作为 \(S[k]\) 就是最优解,时间复杂度 \(O(N\log N+M\log M)\)。

代码

#include<bits/stdc++.h>
using namespace std;
int N,M,T;
long long R[100010],C[100010],S[100010];
long long solve(long long *A,int N)
{
    for(int i=1;i<=N;i++)
    {
        A[i]=A[i]-T/N;
        S[i]=S[i-1]+A[i];
    }
    sort(S+1,S+1+N);
    long long ans=0;
    for(int i=1;i<=N;i++)
        ans=ans+abs(S[i]-S[(N+1)/2]);
    return ans;
}
int main()
{
    cin>>N>>M>>T;
    for(int i=1;i<=T;i++)
    {
        int x,y;
        scanf("%d %d",&x,&y);
        R[x]++,C[y]++;
    }
    if(T%N==0&&T%M==0)
        cout<<"both"<<" "<<solve(R,N)+solve(C,M)<<endl;
    else if(T%N==0)
        cout<<"row"<<" "<<solve(R,N)<<endl;
    else if(T%M==0)
        cout<<"column"<<" "<<solve(C,M)<<endl;
    else
        puts("impossible");
    return 0;
}

posted on 2020-11-17 11:18  DestinHistoire  阅读(141)  评论(0)    收藏  举报

导航