七夕祭(AcWing地址:https://www.acwing.com/problem/content/description/107/)
七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。
于是 TYVJ 今年举办了一次线下七夕祭。
Vani 同学今年成功邀请到了 cl 同学陪他来共度七夕,于是他们决定去 TYVJ 七夕祭游玩。
TYVJ 七夕祭和 11 区的夏祭的形式很像。
矩形的祭典会场由 N 排 M 列共计 N×M 个摊点组成。
虽然摊点种类繁多,不过 cl 只对其中的一部分摊点感兴趣,比如章鱼烧、苹果糖、棉花糖、射的屋……什么的。
Vani 预先联系了七夕祭的负责人 zhq,希望能够通过恰当地布置会场,使得各行中 cl 感兴趣的摊点数一样多,并且各列中 cl 感兴趣的摊点数也一样多。
不过 zhq 告诉 Vani,摊点已经随意布置完毕了,如果想满足 cl 的要求,唯一的调整方式就是交换两个相邻的摊点。
两个摊点相邻,当且仅当他们处在同一行或者同一列的相邻位置上。
由于 zhq 率领的 TYVJ 开发小组成功地扭曲了空间,每一行或每一列的第一个位置和最后一个位置也算作相邻。
现在 Vani 想知道他的两个要求最多能满足多少个。
在此前提下,至少需要交换多少次摊点。
输入格式
第一行包含三个整数 N 和 M 和 T,T 表示 cl 对多少个摊点感兴趣。
接下来 T 行,每行两个整数 x,y,表示 cl 对处在第 x 行第 y 列的摊点感兴趣。
输出格式
首先输出一个字符串。
如果能满足 Vani 的全部两个要求,输出 both;
如果通过调整只能使得各行中 cl 感兴趣的摊点数一样多,输出 row;
如果只能使各列中 cl 感兴趣的摊点数一样多,输出 column;
如果均不能满足,输出 impossible。
如果输出的字符串不是 impossible, 接下来输出最小交换次数,与字符串之间用一个空格隔开。
数据范围
1≤N,M≤100000,
0≤T≤min(N∗M,100000),
1≤x≤N,
1≤y≤M
输入样例:
2 3 4
1 3
2 1
2 2
2 3
输出样例:
row 1
解题关键是行列之间互不影响的性质,于是我们可以单独考虑行、列,再将结果整合。
单独考虑行(列也同理):显然当n不能整除t时,无法操作使得每行感兴趣摊点数一样。而能整除时问题等价于有一个长度为n的序列,我们可以在两个相邻元素之间进行加一减一操作使得最终序列上的每一个数都变为平均值
此时就类似于另一个问题:纸牌均分问题,纸牌均分问题即:M个人排成一排,每个人手中分别有a[1],a[2]…a[n]张牌,每一步操作
某人可以将手中的一张牌分给左/右边的一个人,问最少需要多少步操作最终能使得所有人手中牌数相同。而我们题目中与此不同的是第一个和最后一个摊点可以交换,也就是一个环,先解决非环的分牌问题。显然对于非环的分牌问题,我们每次检查a[i]是否大于平均值avg,若a[i]>avg,则a[i]将a[i]-avg张牌通过a[i]-avg次操作分给a[i+1],若a[i]<avg则a[i]从a[i+1]拿走avg-a[i]张牌,a[i]在操作过程中可能会使a[i+1]变为负数,不过不影响最终结果,因为a[i+1]仍然可以从a[i+2]取走需要的牌,或者可以理解为a[i+2]先将这部分牌分给了a[i+1],不过在程序中我们顺序操作,不需要这样考虑。
则分牌需要的最少步数可由如下代码表示:
res=0;
for(int i=1;i<=n;i++)
{
res+=abs(i*(t/n)-b[i]);
}
//b数组是a数组的前缀和
例如:
a:3 1 2 4 5 ,avg=3
b:3 4 6 10 15
res=0+2+3+2+0=7
又或者我们可以先将原数组每一个数都减去avg,
得到的新数组的前缀和数组结果也和上面方法求出来的结果相同
例如:
a:3 1 2 4 5 ,avg=3
=> a:0 -2 -1 1 2
=> b:0 -2 -3 -2 0
=> res=sum(abs(b[i]))=7 ,1<=i<=5
如果考虑环,也即是第一个数可以和最后一个数相互操作那么3 1 2 4 5会产生比7更小的解,因为选择的起点不同,得到的结果也会不同。
如,我们让环从1处断开,得到新的一条“链”:
a: 2 4 5 3 1
=> a:-1 1 2 0 -2
=> b:-1 0 2 2 0
=>res=1+0+2+2+0=5
于是我们可以考虑,对于给定的一个环, 我们应该从哪个地方断开得到一条最优的链?
再回到刚才的例子:
对于a:3 1 2 4 5
有 b:0 -2 -3 -2 0
对于新的链有:
a:2 4 5 3 1
b:-1 0 2 2 0
新链的数组b相比原来都减了-2,而-2正好是b[1],是我们选择断开环的位置
那即是说我们要在原来的b数组中,找到一个b[i](1<=i<=n),使得对于1<=j<=n范围内的j,abs(b[j]-b[i])的和最小看到这里,相信都知道,我们的b[i]可以选择b数组的中位数,因此我们不需要遍历环去找到一个最优的位置让环断开,而是排序找到b的中位数,最后得到解
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int r[100005],c[100005];
ll cal(int tmp[],int avg,int n){
ll res=0;
for(int i=1;i<=n;i++) tmp[i]-=avg;
for(int i=1;i<=n;i++) tmp[i]+=tmp[i-1];
sort(tmp+1,tmp+n+1);
for(int i=1;i<=n;i++) res+=abs(tmp[i]-tmp[(n+1)/2]);
return res;
}
int main()
{
int n,m;
int t,x,y;
scanf("%d%d%d",&n,&m,&t);
for(int i=0;i<t;i++){
scanf("%d%d",&x,&y);
r[x]++;c[y]++;
}
if(t%n==0&&t%m==0){
ll rs1=cal(r,t/n,n);
ll rs2=cal(c,t/m,m);
printf("both %lld\n",rs1+rs2);
}
else if(t%m==0){
ll rs=cal(c,t/m,m);
printf("column %lld\n",rs);
}
else if(t%n==0){
ll rs=cal(r,t/n,n);
printf("row %lld\n",rs);
}
else printf("impossible\n");
return 0;
}