《算法竞赛进阶指南》七夕祭

《算法竞赛进阶指南》七夕祭

对我来说是一道难题,因为这道题涉及到的模型我都不是很熟悉,而且思维链比较长(一定是我太蒟了)。

1.题意

一个 n*m 矩阵中有 T 个点,判断通过移动相邻位置后,是否能使每行(每列)拥有的点数都相等,并输出最小移动步数。在此题中,第1和n也看作是相邻的。


2.初步分析

若交换的两个位置中均有点,那么这次交换是没有任何意义的。所以,此题中的“交换位置”就是“移动点”。既然如此,那么“点数整除n,m"与”存在一种方案使得每一列/每一行之间点数相同“互为充要条件。下面仅讨论“点数整除n,m"的情况。经过分析,交换左右两个相邻坐标只会改变某两列中的点数。前后也是如此。那么就可以该将问题拆解成“每一列相同 ”“ 每一行相同 ”两个独立的问题。
下面以第一个问题为例进行探讨。
首先预处理出每列中的点数(设为C[i])。


3.“均分纸牌”问题

先介绍一个经典模型:均分纸牌问题。
有m个人共持有T张纸牌排成一行,每一次操作中某一个人可以将一张纸牌交给相邻的一个人,问至少多少次操作才能令每个人的手牌数相等。在有解的情况下,由于第1个人仅能从第2个人手中拿牌,故我们可以先考虑第一个人:
1.若C[1]>T/m,则第一个人需要将C[1]-T/m张纸牌给第2个人
2.若若C[1]<T/m,则第1个人需要从第2个人手中拿Y/m-C[1]张牌。
以此类推,可以发现达到目标所需要的最小步数其实就是:∑|i*T/m-G[i]| (1<=i<=m),其中G[i]是C的前缀和。
设A[i]=C[i]-T/m,即先将所有人的手牌减掉T/m,最后令每个人手中恰有0张牌,答案显然不变,即:
∑|S[i]|,其中S[i]是A的前缀和。
从数学的角度,上述两式也是相等的。
到这里,一条直线上的”均分纸牌”问题就解决了。


4.货仓选址问题

简单来说,就是数轴上有x个点,询问该数轴上到这些点的距离和最小的位置。
即令Σ| a[i]-k |最小的k值
结论就是:中位数
因为只有在这里,无论向左向右都不会使答案更优。
这个问题很好理解,也是比较常见的模型。


5.回归正题

到这里,我们可以发现,这里探讨的问题就是环上的“均分纸牌”问题。运用“断环为链”的思想,想到一种朴素的方法为:枚举分界点,转化成一般的”均分纸牌“问题。然后考虑在这个基础上对分界点的查找进行优化。
设在第1个人与第m个人之间断开,此时问题与“均分纸牌”问题一致。
纸牌数与前缀和分别设为A[1],A[2]……A[n],S[1],S[2]……S[n]
类似地,以上述断环方式为基准,如果在第k个人之后断环,则这m个人持有的纸牌数与前缀和分别为:
A[k+1] —— S[k+1]-S[k]
A[k+2]—— S[k+2]-S[k]
……
A[m]—— S[m]-S[k]
A[1]——S[1]+S[m]-S[k]
……
A[k] —— S[k]+S[m]-S[k]
其中,根据A[ ]及S[ ]的定义,S[m]=0,该项可略去。
据此,从第k个数往后断开的最小步数为:
Σ| S[i]-S[k] |,其中S为A的前缀和,即S[i]=∑A[j] (1<=j<=i )
那么k在何时使得答案最小?
这就是货仓选址问题!
所以,S[k]取到中位数时有最优解。
然后这个题就搞定了

code(下面是一组hack数据)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define il inline
#define re register
#define Abs(x) ((x<0)?-(x):x)
il int read()
{
	int s=0,w=1;char c=getchar();
	while(c<'0'||c>'9'){ if(c=='-') w=-1;c=getchar();}
	while(c>='0'&&c<='9'){ s=(s<<1)+(s<<3)+c-'0';c=getchar();}
	return s*w;
}
const int N=1e5+10;
int n,m,T,X[N],Y[N],cx[N],cy[N];
LL ans;
int a[N],sum[N]; 
/*
cx[i]表示在第i行的点有多少个 
cy[i]列 
*/
il void calc(int *c,int M)
{
	int tmp=T/M;
	for(re int i=1;i<=M;i++){
		a[i]=c[i]-tmp,sum[i]=sum[i-1]+a[i];
	}
	sort(sum+1,sum+1+M);
	if(M&1){
		int K=sum[M+1>>1];
		for(re int i=1;i<=M;i++) ans+=Abs(sum[i]-K);
	} 
	else{
		int K=sum[M>>1];
		LL tmp1=0,tmp2=0;
		for(re int i=1;i<=M;i++) tmp1+=Abs(sum[i]-K);
		K=sum[(M>>1)+1];
		for(re int i=1;i<=M;i++) tmp2+=Abs(sum[i]-K);
		ans+=min(tmp1,tmp2);
	}
} 
int main()
{
	n=read(),m=read(),T=read();
	for(re int i=1;i<=T;i++){
		X[i]=read(),Y[i]=read();
		cx[X[i]]++,cy[Y[i]]++;
	}
	if(T%m){
		if(T%n){ printf("impossible");return 0; }
		else printf("row ");
	}
	else{
		if(T%n) printf("column ");
		else printf("both ");
	}
	if(T%n==0) calc(cx,n);
	if(T%m==0) calc(cy,m);
	printf("%lld",ans);
}
/*
谷仓选址+卡牌游戏 
先处理出a[i]=c[i]-T/M的前缀和数组sum[i] 
然后选出sum[i]中的中位数 ,计算到这个中位数的距离 
*/ 
/*
10 10 50
6 3
10 5
8 8
5 3
8 9
2 9
6 6
9 1
4 7
2 3
9 9
1 5
6 9
4 4
10 4
9 2
1 10
8 1
10 9
6 4
4 9
1 3
6 1
7 5
8 6
8 7
2 7
10 8
4 10
7 7
7 10
10 10
6 10
5 9
3 3
3 9
9 4
1 2
5 8
4 5
8 5
6 2
7 2
10 3
8 10
9 6
9 8
6 8
8 2
10 2

both 33
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值