传送门
竟然是权限题!!!
目前正在学习的lydrainbowcat著《算法竞赛进阶指南》里面的题目,里面的讲解非常好。强推这本书,很棒!
题意和题目都灰常棒!好评!
非常有意思!
以下题解出自《算法竞赛进阶指南》(有改动因为我不想抄写。。和版权也有关):
我们发现:
交换左右两个相邻的摊点只会改变某两列中cl感兴趣的摊点数而对这一行没有影响。
相同的,交换上下两个相邻的摊点只会改变某两行中cl感兴趣的摊点数对这一列没有影响。
所以我们可以将题目拆分成两个问题分别计算:
1、通过左右交换最少次数使得每列中cl感兴趣的点相同
2、通过上下交换最少次数使得每行中cl感兴趣的点相同
以第一个问题为例,首先判断可不可行,因为每列要一样,所以如果T不能被m整除,那么则条件不可行,即列不可能达到要求。
可行的话:
我们在输入的时候可以统计一开始每列中cl感兴趣的摊点总数,记为c1~cm,那么他们应该是不均匀的,这时候我们要花最小的代价把他们调配均匀,这就契合了一个经典问题:均分纸牌。
在这里再讲一讲均分纸牌:
均分纸牌就是有m个人,每个人有c[i]个纸牌,可以给左右两边的人牌,我们最少需要多少次数使得每个人手里的纸牌数一样。
首先最后人们手里的纸牌数都会为平均数avg也就是T/m(保证T可以整除m)
然后让a[i]=c[i]-avg也就是说这个人最少需要运动a[i]步使得他手中的牌等于平均数
那么我们对a[i]做一个前缀和suma[i],表示在1~i这个区间的所有牌转移出去所需要的最小步数,那么我们对这个suma[i]的绝对值(有负数啊)做一次求和sum,就表示我们把所有区间的牌都转移到了其他区间,那么此时就是均分状态。
那么这时候就有个问题了,负数呢??其实我们给别人一个负数的牌,就相当于别人给我们一个这个负数的相反数这么多的牌,所以它可以抵消正数,可以直接累加处理。
那么结果就是:
设
a[i]=c[i]−avg
a
[
i
]
=
c
[
i
]
−
a
v
g
suma[i]=∑ij=1a[j]
s
u
m
a
[
i
]
=
∑
j
=
1
i
a
[
j
]
那么需要的最少步数就是
∑mi=1|suma[i]|
∑
i
=
1
m
|
s
u
m
a
[
i
]
|
然而这道题并不是单纯的均分纸牌,而是“环形均分纸牌”,因为头和尾是连在一起的。
那么我们就需要找个地方把环断开,设这个位置为k(对于环常用枚举断点),那么相对于断点在0而言(也就是普通的均分纸牌,我们可以把他视作断点在0)前缀和就需要减掉
suma[k]
s
u
m
a
[
k
]
根据上方的公式答案即为
∑mi=1|suma[i]−suma[k]|
∑
i
=
1
m
|
s
u
m
a
[
i
]
−
s
u
m
a
[
k
]
|
当k取什么值最小又成为了问题,这与书中提到的“货仓选址”问题有关,这里不赘述,大家经过思考也应该能很快想出来k应该是在中位数的位置最优。
那么排序后找到中位数k问题即可迎刃而解。
书中提到:本题可以转换成多个经典的简单问题求解。我们应该熟练基础问题,触类旁通,将各种模型之间的简化,拓展和联系作为算法学习与设计的脉络。
感触良多。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=100010;
int x[N],y[N],s1[N],s2[N];
int main()
{
int n,m,T;scanf("%d%d%d",&n,&m,&T);
for(int i=1;i<=T;i++)
{
int a,b; scanf("%d%d",&a,&b);
x[a]++,y[b]++;
}
if(T%m!=0 && T%n!=0)
{
printf("impossible\n");
return 0;
}
for(int i=1;i<=n;i++) x[i]-=T/n;
for(int i=1;i<=m;i++) y[i]-=T/m;
long long ans=0;
if(T%n==0)
{
for(int i=1;i<=n;i++) s1[i]=s1[i-1]+x[i];
sort(s1+1,s1+n+1);
long long k=s1[(n+1)/2];
for(int i=1;i<=n;i++)
ans+=abs(k-s1[i]);
}
if(T%m==0)
{
for(int i=1;i<=m;i++) s2[i]=s2[i-1]+y[i];
sort(s2+1,s2+m+1);
long long k=s2[(m+1)/2];
for(int i=1;i<=m;i++)
ans+=abs(k-s2[i]);
}
if(T%n==0 && T%m==0) printf("both ");
else if(T%n==0 && T%m!=0) printf("row ");
else printf("column ");
printf("%lld\n",ans);
return 0;
}