P7115 [NOIP2020] 移球游戏 解法

本文同步发于洛谷

题目链接

题意

有 \(n+1\) 根柱子,每个柱子能放下 \(m\) 个球,其中前 \(n\) 个上放满了颜色不同的球,共 \(n\) 种颜色,每种颜色 \(m\) 个球,每次可将某个柱子顶部的球移至另一个没放满的柱子的顶部,求任意一个将同种颜色的球均放在同一个柱子上的方案数不超过 820000 的方案。

做法

70 pts

考虑将所有柱子的某种颜色的球都移至顶部,对于每个柱子需要另外两个一满一空的帮助。

在此基础上我们再加一点常数上的优化,每次只移动方案数最优的那个颜色,再加一点其他对不必要操作的减少,即可获得 70 pts,比较考验码力。

正解

我们考虑 \(n=2\) 的做法。

先算出第一个柱子颜色较少的那个的数量,将第二个柱子的同数量的球移到空柱,然后将第一个柱子的球分类,再将同一颜色的放回第一个柱子,再将第二个柱子的球也分了,这样就能解决了。操作次数为:\(5m-s\),其中 \(s\) 是第一个柱子中颜色较少的那个的数量。

然后我们考虑将这种做法扩展至 \(n>2\):

我们可以采用归并分治的思想,将颜色 \(\le mid\) 的看作 \(1\),将 \(>mid\) 的看作 \(2\)。那么,分治到最后,整个球也都分类完了。

计算方案数:

\[T(n)=2\times T(\frac{n}{2})+5\times m\times n \]

\[T(n)=5\times m\times n\times logn \]

可以通过本题!

具体实现就看代码吧!

code

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
inline int rd(){
    int x=0,id=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')id=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*id;
}
int n,m,a[55][405],ans[800003][2],top[55],tot;
bool fg[55];
inline void op(int x,int y){
	ans[++tot][0]=x,ans[tot][1]=y,a[y][++top[y]]=a[x][top[x]--];
}
void sol(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;
	memset(fg,0,sizeof(fg));
	for(int i=l;i<=mid;i++)//将其分成两种,小于等于mid,大于mid 
		for(int j=mid+1;j<=r;j++){
			if(fg[i]||fg[j])continue;
			int s=0;
			for(int k=1;k<=m;k++)s+=(a[i][k]<=mid);
			for(int k=1;k<=m;k++)s+=(a[j][k]<=mid);
			if(s>=m){
				s=0;
				for(int k=1;k<=m;k++)s+=(a[i][k]<=mid);//一顿操作猛如虎 
				for(int k=1;k<=s;k++)op(j,n+1);
				while(top[i])a[i][top[i]]<=mid?op(i,j):op(i,n+1);
				for(int k=1;k<=s;k++)op(j,i);
				for(int k=1;k<=m-s;k++)op(n+1,i);
				for(int k=1;k<=m-s;k++)op(j,n+1);
				for(int k=1;k<=m-s;k++)op(i,j);
				while(top[n+1]){
					if(top[i]==m||a[n+1][top[n+1]]>mid)op(n+1,j);
					else op(n+1,i);
				}
				fg[i]=1;
			}else{
				s=0;
				for(int k=1;k<=m;k++)s+=(a[j][k]>mid);//反向 
				for(int k=1;k<=s;k++)op(i,n+1);
				while(top[j])a[j][top[j]]>mid?op(j,i):op(j,n+1);
				for(int k=1;k<=s;k++)op(i,j);
				for(int k=1;k<=m-s;k++)op(n+1,j);
				for(int k=1;k<=m-s;k++)op(i,n+1);
				for(int k=1;k<=m-s;k++)op(j,i);
				while(top[n+1]){
					if(top[j]==m||a[n+1][top[n+1]]<=mid)op(n+1,i);
					else op(n+1,j);
				}
				fg[j]=1;
			}
		}
	sol(l,mid);sol(mid+1,r);
}
int main(){
	n=rd(),m=rd();
	for(int i=1;i<=n;i++)
		for(int j=1,x;j<=m;j++)x=rd(),a[i][++top[i]]=x;
	sol(1,n);
	cout<<tot<<"\n";
	for(int i=1;i<=tot;i++)cout<<ans[i][0]<<" "<<ans[i][1]<<"\n";
	return 0;
}
posted @ 2026-10-28 15:07  yjimf  阅读(4)  评论(0)    收藏  举报