题意 :有n行m列的矩阵,有t个特殊点,只能通过上下或者左右交换,求使这个矩阵变成每行或者每列或者每行每列的特殊点都一样多,至少需要交换多少次。
分析 :
-
将每行每列的特殊点个数相加,交换行只改变列的特殊点的个数,交换列也只能改变每行的特殊点的个数。
-
这可以将两个数列提出来用两个数组储存行(H[n])列(L[m])的特殊点数,再进行一些操作(+1 -1),使得每个位置上的数字都一样.
-
这个问题就演变成了经典问题“均分纸牌”(两个均分纸牌n和m),只不过比“均分纸牌”问题多了一个条件:初始位置和结尾位置是可以相连的,那么我们可以按照“均分纸牌”问题来分析这个题目。
-
均分纸牌 :有n个人排成一列,他们手中分别有一些牌,在每一步操作中,可以让一个人的牌交到他旁边人的手里,求最少需要多少步才能使每个人手上的牌相等。
-
我们先来分析“均分纸牌”问题怎么解,假设这个问题有解(纸牌总数 % 总人数 == 0)。因为每个人都是固定的位置,如果第一个人的牌小于平均纸牌数,那么第一个人的牌只能从第二个人手中获得,如果第一个人的牌大于平均数,那么第一个人的牌只能交给第二个人。当第一个人达到平均数的时候,第二个人将不能再交给第一个人或者向第一个人索要,所以第二个人又会重复第一个人的操作,直至到最后一个人时,他后面没有人了,所以这个时候所有人都已经达到了平均数,问题结束。
-
设A[1]~A[n]是第1个人到第n个人手中所持有的纸牌数,S[1] ~ S[n]是纸牌的前缀和,设纸牌平均数是C,C - A[1] 就是代表第一个人要得到这么多纸牌能到达平均数(可正可负),那么第二个人就需要C - [ A[2] - ( C - A[1] ) ] 这么多纸牌才能达到平均数,化简得2C - (A[1] + A[2]),一直到A[n]都是这样,所以得到以下公式。
n ∑ |iC-S[i]| i=1
-
如果将每个人的纸牌数都减去平均值,现在A[i] = A[i] - C ,S[i]是A[i]的前缀和那么公式就会变成
n ∑ |S[i]| i=1
-
上面的公式只适用于首尾不能交换的情况,那我们来考虑首尾能交换的情况,有种暴力的方法就是枚举第1~n个人,让他们变成第一个人,再对这些人操作,这样必有一个最优解。
-
另一种方法:在1~n中取一个k,令第k个人变成第一个人,那么A数列排列成
A[k+1],A[k+2]…A[n],A[1]…A[k] , S数列排列成
S[k+1]-S[k] , S[k+2]-S[k] … S[n]-S[k] , S[1]+S[n]-S[k] … S[k]-S[k]
因为S[n]等于0,所以相当与在S[1]~S[n]的基础上减去S[k]。n ∑ |S[i]-S[k]| i=1
-
也就相当于求k(1~n)取何值时以上的值最小
-
这个公式可以看作有一个数列1~n,要在其中选择一个元素作为一个固定点,求每个点到固定点的最短总距离,这就是“货仓选址”。
-
货仓选址 :在一条数轴上有n家商店,他们的地址分别是A[1]~A[n],现在要在数轴上建立一个货仓,从货仓到每家商店都要运送一车商品。为了提高效率,求把货仓建在什么何处,可以使得货仓到每家商店的距离之和最小。
-
先将A[1]~A[n]排序,距离之和最小的位置就是中位数,假设为k(n为奇数,偶数有两个),如果将货仓建在中位数的左边,假设A[k]左边的离A[k]的距离是x,那么左边的距离会少x*(k-2),右边的距离会增加x*(k-1),所以总距离增加了x。
-
所以回到本题,就可以看作是一个“货仓选址”问题,只需要将S排序找到中位数k,即可求出本体答案。
总结:本题 = 均分纸牌 + 货仓选址
//因为bzoj3032题目显示不出来,所以只写了个大致
#include<iostream>
using namespace std;
const int N = 1e5+5;
int main()
{
int n,m,k,x,y;
int H[N]={0},L[N]={0},S[N]={0};
cin>>n>>m>>k;
for(int i=1;i<=k;i++){
cin>>x>>y;
H[x]++,L[y]++;
}
int avgh=0,avgl=0;
for(int i=1;i<=n;i++)
avgh += H[i];
avgh /= n;
for(int i=1;i<=m;i++)
avgl += L[i];
avgl /= m;
//减去平均值的H和L再算出S
if(k % n == 0){
//排序S找中位数,要用结构体cmp排序 懒得写了
}
if(k % m == 0){
}
//判断输出哪一种结果
return 0;
}