1. 程式人生 > 實用技巧 >NOIP2020 T3移球遊戲

NOIP2020 T3移球遊戲

題目描述

\(n\)個柱子,每個柱子上面\(m\)個大小相同、顏色不同的球,尋找一種移球方案使得每個柱子上面的球顏色相同。

題解

這題思維難度比較高,程式碼實現難度不大,考場打了一個假掉的做法,看了正解以後感覺非常之妙。

做法挺自然的,首先考慮只有兩種顏色的情況:

  • 考慮將所有黑色的球移動到白色的球上方。具體來說:先將要操作的柱子找出來,找到其裡面有多少個黑球(設有\(k\)個),將隨便另外一個柱子最上方的\(k\)個球移到空柱上,然後操作這個柱子,將黑球移到我們選的柱子上,白球移到空柱上,再將黑球和白球先後移會來,因為每時每刻空餘位置數都是\(m\),所以是合法的。
  • 然後考慮將每種顏色的球移動到一個柱子上。可以考慮對於每一個要操作的柱子,找到另外一個要操作的柱子,分黑球數之和大於\(m\)
    和小於\(m\)來討論,就可以容易實現。

對於\(n>2\)的情況,可以考慮分治,將所有球染為黑白兩色,同顏色的也染為同顏色,然後給這些球各自分配一半的柱子,然後繼續遞迴下去就可以解決了。大概計算一下,總共的移球次數是在\(800000\)左右。

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 60
#define M 500
using namespace std;
int n,m,i,j,c[N][M],ans,ans1[1000000][3],num,id[M],cnt[N];
int read(){
	int x=0;
	char ch=getchar();
	while (ch>'9'||ch<'0') ch=getchar();
	while (ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
void move(int x,int y,int num){
	int i;
	for (i=1;i<=num;i++){
		cnt[x]--;cnt[y]++;
		c[y][cnt[y]]=c[x][cnt[x]+1];c[x][cnt[x]+1]=0;
		ans++;
		ans1[ans][1]=x;ans1[ans][2]=y;
	}
}
void dfs(int l,int r){
	int mid=(l+r)/2,i,j,sum,x,y,s1,s2;
	if (l==r) return;
	num=0;
	for (i=1;i<=n;i++)
		if (c[i][1]>=l&&c[i][2]<=r)
			id[++num]=i;
	for (i=1;i<=num;i++){
		sum=0;
		x=id[i];y=id[i%num+1];
		for (j=1;j<=m;j++)
			if (c[x][j]>=l&&c[x][j]<=mid) sum++;
		if (!sum) continue;
		move(y,n+1,sum);
		s1=s2=0;
		for (j=m;j>=1;j--){
			if (s1>=sum) break;
			if (c[x][j]>=l&&c[x][j]<=mid){
				s1++;
				move(x,y,1);
			}
			else{
				s2++;
				move(x,n+1,1);
			}
		}
		move(n+1,x,s2);
		move(y,x,s1);
		move(n+1,y,s1);
	}
	for (i=1;i<=num-1;i++){
		x=id[i];y=id[i%num+1];
		s1=s2=0;
		for (j=1;j<=m;j++){
			if (c[x][j]>=l&&c[x][j]<=mid) s1++;
			if (c[y][j]>=l&&c[y][j]<=mid) s2++;
		}
		if (s1==m||s1==0) continue;
		if (s1+s2>m){
			move(x,n+1,m);
			move(y,x,s2);
			move(n+1,y,m-s1);
			move(n+1,x,m-s2);
			move(n+1,y,cnt[n+1]);
		}
		else{
			move(x,n+1,s1);
			move(y,n+1,s2);
			move(y,x,s1);
			move(n+1,y,s1+s2);
		}
	}
	dfs(l,mid);dfs(mid+1,r);
}
int main(){
	freopen("ball.in","r",stdin);
	freopen("ball.out","w",stdout);
	n=read();m=read();
	for (i=1;i<=n;i++){
		cnt[i]=m;
		for (j=1;j<=m;j++)
			c[i][j]=read();
	}
	dfs(1,n);
	printf("%d\n",ans);
	for (i=1;i<=ans;i++) printf("%d %d\n",ans1[i][1],ans1[i][2]);
	return 0;
}