NOIP2020正式赛 移球游戏(ball)题解

在这里插入图片描述

本文参考了洛谷Dzhao大佬的题解

首先,做这种构造题,一定要想出一种普适性的方法,不能去想所谓的“最优解”,而是要去想一种在不同的情况下都能用的方法。
其次,部分分是一种提示满分的思路。
如果只有2个柱子,怎么做呢?(设颜色为0,1)
柱子1 m个球
柱子2 m个球
柱子3 0个球
Step 1
首先设柱子1的m个球中有sum个1
将柱子2的顶上sum个球移到柱子3
那么情况变成
柱子1 m个球
柱子2 m-sum个球
柱子3 sum个球
Step 2
将柱子1中球一个个提出来,是颜色1放柱子2上,颜色0放柱子3上
情况变成
柱子1 0个球
柱子2 m-sum+sum=m个球
柱子3 sum+m-sum=m个球
Step 3
现在柱子2顶上有sum个颜色为1的球,将它们移到柱子1
对于柱子3顶上m-sum个颜色为0的球同理
然后将柱子2的球全部移到柱子3
情况变成
柱子1 m个球(sum个连续1,m-sum个连续0)
柱子2 0个球
柱子3 sum+m-sum=m个球
Step 4
将柱子1顶上m-sum个颜色为0的球移到柱子2
情况变成
柱子1 sum个连续颜色为1的球
柱子2 m-sum个连续颜色为0的球
柱子3 m个球
我们现在完成了将颜色0,1球分离的伟大壮举
Step 5
移动柱子3的球,颜色为1的移到柱子1,颜色为0的移到柱子2
步骤完成,现在柱子1上就是m个颜色为1的球,柱子2上就是m个颜色为0的球,柱子3仍然是空的。
在n=2时,我们得到了一个普适性的方法。
如何扩展n的范围?
我们考虑分治。
钦定一个数,使区间内小于等于它的数设为1,大于设为0
这个数取区间中点最优
那么每次对于区间,枚举处理两根交换的柱子跨过区间中点的情况,然后分治递归继续向下处理即可

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 55
#define M 405
#define Lim 820005
using namespace std;
int len[N],a[N][M],bz[N];
int i,j,k,m,n,o,p,l,s,t,flag;
int ans[Lim][2],tot;
void read(int &x)
{
	char ch=getchar();x=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
}
void move(int i,int j) {ans[++tot][0]=i,ans[tot][1]=j,len[j]++,a[j][len[j]]=a[i][len[i]],len[i]--;}
void dg(int l,int r)
{
	if (l==r) return;
	int mid=l+r>>1;
	memset(bz,0,sizeof(bz));
	for (int pos1=l;pos1<=mid;pos1++)
		for (int pos2=mid+1;pos2<=r;pos2++)
		{
			int i=pos1,j=pos2;
			if (bz[i]||bz[j]) continue;
			s=flag=0;
			for (int k=1;k<=m;k++) s+=(a[i][k]<=mid)+(a[j][k]<=mid);
			if (s<m) swap(i,j),flag=1;
			s=0;
			for (int k=1;k<=m;k++) s+=(!flag?a[i][k]<=mid:a[i][k]>mid);
			for (int k=1;k<=s;k++) move(j,n+1);
			for (k=len[i];k>=1;k--)
			{
				k=len[i];
				if ((!flag?a[i][k]<=mid:a[i][k]>mid)) move(i,j);
				else move(i,n+1);
			}
			for (int k=1;k<=s;k++) move(j,i); 
			for (int k=1;k<=m-s;k++) move(n+1,i);
			for (int k=1;k<=m-s;k++) move(j,n+1);
			for (int k=1;k<=m-s;k++) move(i,j);
			for (k=len[n+1];k>=1;k--)
			{
				k=len[n+1];
				if (len[i]!=m&&((!flag?a[n+1][k]<=mid:a[n+1][k]>mid)||len[j]==m)) move(n+1,i);
				else move(n+1,j);
			}
			bz[i]=1;
		}
	dg(l,mid),dg(mid+1,r);
}
int main()
{
	freopen("ball.in","r",stdin);
	freopen("ball.out","w",stdout);
	read(n),read(m);
	for (i=1;i<=n;len[i]=m,i++)
		for (j=1;j<=m;j++)
			read(a[i][j]);	
	dg(1,n);
	printf("%d\n",tot);
	for (i=1;i<=tot;i++) printf("%d %d\n",ans[i][0],ans[i][1]);
	return 0;
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值