七夕祭

七夕祭

给出一个\(n\times m\)的网格图,以及t个棋子的位置,每次操作可以选择将一个棋子向上下左右移动,注意第一行与第n行相邻,第一列与第n列相邻,如棋子可以从第一行移动到第n行,现在有2个条件

  1. 保证每一行的棋子数相同
  2. 保证每一列的棋子数相同

询问尽量满足上诉要求的最少移动次数,\(1≤n,m≤100000\)

注意到一个性质,当棋子上下移动的时候,只会改变每一行拥有的棋子数,而棋子左右移动只会改变每一列拥有的棋子数,换个意思就是棋子的上下移动和左右移动可以作为两个问题看待,也就是要求可以分别处理,这样问题就大大简化了,接下来设棋子数为\(tot\)

于是设\(h[i]\)表示第i行拥有的棋子数,\(l[i]\)为第i列拥有的棋子数,不妨拿行出来研究,现在的问题转化成,数列\(\{h[i]\}\)的中间一个位置可将自己的部分数字交给它相邻的位置,而这个数字就是操作次数,每一行要平均分到\(tot/n\),如果\(tot\% n\)不为0显然不能均分,显然这个问题类似均分纸牌,如果设\(\{H_i\}\)\(\{h_i\}\)的前缀和,那么对于数列前i个数来看,它拥有的数有\(H_i\),但是它只需要\(tot/n\times i\),于是第i+1个数要交给或接受这一个整体\(|H_i-tot/n\times i|\),于是容易知道当不存在第一行与最后一行相邻的时候,我们有答案\(\sum_{i=1}|H_i-tot/n\times i|\)

但是注意相邻,类似一个环,于是我们可以考虑处理环的基本办法,这道题采取了二次递推,名不符实,因为二次递推是递推里的做法,但基本思想都是拆环以后,根据一个拆环的维护的数据来推出其他拆环的数据(犹如二次扫描+换根法)

\[h_{k+1},h_{k+2},...,h_n,h_1,...,h_k \]

此时按照均分纸牌的思路容易知道,答案应该为

\[|h_{k+1}-n/tot|+|h_{k+1}+h_{k+2}-n/tot\times 2|+...+|h_{k+1}+...+h_{n}-n/tot\times (n-k)|+ \]

\[|h_{k+1}+...+h_n+h_1+n/tot\times (n-k+1)|+...+|h_{k+1}+...h_n+h_1+...+h_k-n/tot\times n| \]

很难想到设\(f_i=H_i-tot/n\times i\)的前缀和,于是有

\[|f_{k+1}-f_k|+|f_{k+2}-f_k|+...+|f_n-f_k|+|f_n-f_k+f_1|+...+|f_n-f_k+f_k| \]

因为\(f_i\)的绝对值表示前i个位置需要第i+1个位置所或者给的数字,于是\(f_n=0\),因为前n个位置不要得到数字,否则无解(从代数上来看\(f_n=H_n-tot/n\times n=H_n-tot=tot-tot=0\)),所以有

\[|f_{k+1}-f_k|+|f_{k+2}-f_k|+...+|f_n-f_k|+|f_1-f_k|+...+|f_k-f_k|= \]

\[\sum_{i=1}^n|f_i-f_k| \]

现在问题就转化成了求出哪个\(f_k\)可以使这个式子最小化,显然这就是列车调度,取中位数即可,时间复杂度可以做到\(O((n+m)log(n))\)

我已经很久不小结题目了,但是此题实在是让我认识到\({\large \text{我太菜了}}\)

先总结一下网格图的基本做法

首先它的考虑点应该是从行列,对角线,矩形,轮廓来考虑的(细节是\(1\times n,n\times 1\)的网格图,你该输出什么)

而一些比较骚的做法是拆行成列(八数码问题,高斯消元)和行列独立(看看行列之间的关系是否独立,例子比如此题还有一大堆错排问题)

然后就是环的问题处理,首要拆环成链,然后考虑是要再补一截,记录一截还是二次递推

而接下来的代数变换就骚的教我做人,根本想不到设哪个为一个整体,然后就推,发现原来是一个列车调度问题。

于是根据此题,我们得出一个著名的结论,\({\large \text{我太菜了}}\)

参考代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#define il inline
#define ri register
#define ll long long
using namespace std;
ll ans;
int g[100001],k[100001],tot,check;
il void read(int&),work(int,int[]);
template<class free>
il free Abs(free);
int main(){int n,m,t;
	read(n),read(m),read(t);
	for(int i(1),y,x;i<=t;++i)
		read(y),read(x),++g[y],++k[x],++tot;
	if(!(tot%n))check|=1,work(n,g);if(!(tot%m))check|=2,work(m,k);
	switch(check){
	case 0:cout<<"impossible ";break;
	case 1:cout<<"row ";break;
	case 2:cout<<"column ";break;
	case 3:cout<<"both ";break;
	}if(check)printf("%lld",ans);
	return 0;
}
template<class free>
il free Abs(free x){
	return x<0?-x:x;
}
il void work(int n,int a[]){
	for(int i(1);i<=n;++i)
		a[i]-=tot/n,a[i]+=a[i-1];
	sort(a+1,a+n+1);int mid(a[1+n>>1]);
	for(int i(1);i<=n;++i)
		ans+=Abs(mid-a[i]);
}
il void read(int &x){
	x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

posted @ 2019-07-19 16:19  a1b3c7d9  阅读(145)  评论(0编辑  收藏  举报