P7115 [NOIP2020] 移球游戏

\(\mathcal Link\)

很有意思的题目,并没有想象的那么难。

首先,为了方便起见,我们可以认为只有两种颜色的球,记为 \(0/1\)。考虑如何将 \(0/1\) 分开,之后多次重复这一过程,每次将部分颜色的球看作 \(1\) 即可。

考虑到要将所有颜色移到同一列中,首先要将将一个目标列中的 \(0/1\) 给“分离”出来。
方法很简单:考虑该列中 \(1\) 的个数为 \(x\),则如下操作:

  • 将一个满列上方的 \(x\) 个球移至空列 (\(x\) 步)
  • 将目标列的 \(0\) 放入空列,\(1\) 放入满列(分离)(\(m\) 步)
  • 将这些球“倒回”目标列( \(m\) 步)
  • 将空列球还原(\(x\) 步)

一共有 \(2m+2x\) 步。这是还原的基本操作,称为“提取”。

如果直接利用该过程暴力执行,进行一些优化可以获得 \(70pts\)

然后,就有几种不同的思路:

一 (感谢 QwQcOrZ 提供)

可以发现,这种方法操作次数与限制次数相差常数级别。

原来的做法还有优化空间,因为我们发现其实用 \(m+x\) 已经成功分离了该列的 \(0/1\)。因此,考虑如何“不还原”。方法很简单:让所借助的列为全 \(0\) 列。

因为 \(0\) 的数量远大于 \(1\) 所以构造很简单:分离第一列后再分离第二列即可(前两列中 \(0\) 的个数 \(\geq m\))。

这样,常数就优化了一半,上界 \(60,0000\) 次。

当然,要特判 \(n=2\) 的情况,具体操作可在方法二中找到。

二 (大多数题解的分治方法)

考虑将“颜色”的编号设一个阈值,超过为 \(0\),未超过为 \(1\)

考虑每次将序列划成两段,左边为 \(0\),右边为 \(1\),再分治左右两边。

只需考虑“划分 \(0/1\)”即可。用类似双指针的过程每只考虑两列。

考虑以多补少。用错误的球较多的一列( \(i\) )补齐较少的一列( \(j\) )。

\(j\) 进行“提取”操作(错误球在上方),并将错误球放入空列,再分离 \(i\) 列(将与 \(j\) 同色的球放入 \(j\) 列,其余放入 \(n+1\) 列),最后将 \(n+1\) 中的球还原到 \(i\) 的位置。

用官方数据测试没有超过 \(47,0000\) 次的。

#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
using namespace std;
char buf[1<<14],*p1=buf,*p2=buf;
#define GetC() ((p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<14,stdin),p1==p2)?EOF:*p1++)
struct Ios{}io;
template <typename _tp>
Ios &operator >>(Ios &in,_tp &x){
	x=0;int w=0;char c=GetC();
	for(;!isdigit(c);w|=c=='-',c=GetC());
	for(;isdigit(c);x=x*10+(c^'0'),c=GetC());
	if(w) x=-x;
	return in;
}
const int N=55,M=405;
int a[N][M];
int col[N][M];
int t[N];
int top[N];
int bel[N];
int n,m;
vector<pair<int,int> > v;
void move(int i,int j,int k=1){
	while(k--){
		v.push_back(make_pair(i,j));
		a[j][++top[j]]=a[i][top[i]--];
		int x;
		x=col[j][top[j]]=col[i][top[i]+1];
		t[i]-=(x==bel[i]);
		t[j]+=(x==bel[j]);
	}
}
void tiqu(int i,int j){//use j to sort i
	int x=t[i];
	move(j,n+1,x);
	while(top[i]>0){
		if(col[i][top[i]]==bel[i]) move(i,j);
		else move(i,n+1);
	}
	move(j,i,x);move(n+1,i,m-x);
	move(n+1,j,x);
}
void work(int i,int j){
	if(m-t[i]<m-t[j]) swap(i,j);
	tiqu(j,i);
	move(j,n+1,m-t[j]);
	while(top[j]!=m){
		if(col[i][top[i]]==bel[j]) move(i,j);
		else move(i,n+1);
	}
	move(n+1,i,top[n+1]);
}
void solve(int l,int r){
	if(l==r) return ;
	int mid=(l+r)>>1;
	for(int i=l;i<=r;++i){
		t[i]=0;
		if(i<=mid) bel[i]=0;
		else bel[i]=1;
		for(int j=1;j<=m;++j){
			col[i][j]=(a[i][j]>mid);
			t[i]+=(col[i][j]==bel[i]);
		}
	}
	int i=l,j=mid+1;
	while(i<=mid&&j<=r){
		while(i<=mid&&t[i]==m) ++i;
		while(j<=r&&t[j]==m) ++j;
		if(i>mid||j>r) break;
		work(i,j);
		if(t[i]==m) ++i;
		if(t[j]==m) ++j;
	}
	solve(l,mid);solve(mid+1,r);
}
int main(){
	io>>n>>m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			io>>a[i][j];
	for(int i=1;i<=n;++i) top[i]=m;
	solve(1,n);
	printf("%d\n",(int)v.size());
	for(auto tmp:v) printf("%d %d\n",tmp.first,tmp.second);
	return 0;
}
posted @ 2022-11-18 16:11  pref_ctrl27  阅读(141)  评论(0编辑  收藏  举报