NOIP 2020 T3 移球游戏(构造+分治)

本文解析了NOIP2020 T3移球游戏中高效的分治策略,通过空柱操作复原颜色,介绍了如何在计算机上实现步骤,以及代码示例。核心在于理解颜色排序和部分分治技巧,时间复杂度为O(nlogn * m)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NOIP 2020 T3 移球游戏

题解

  • 如果能想到一个个位置复原的话,可以拿到前404040分。
  • 方法其实并不难,只要能想到要这么做,相当于考虑如何交换任意两个位置,借助空柱操作即可。
  • 正解的方向很清晰,毕竟题目的部分分给了很明显的提示,
  • 容易想到,可以用分治,把左右各自归类,然后递归下去,此时的问题是如何实现归类,也就是如何实现部分分中的n=2n=2n=2
  • 先大概捋一捋过程:左右两区间各一个指针从左往右,将当前指针指向的两列中,较多的一种颜色复原,并将其对应的指针后移,以此类推。
  • 如何时间复原较多颜色的过程?
  • 当颜色都是乱序时不方便处理,可以先让这两列的颜色有序。
  • 不妨分成两个步骤,一是使颜色多的在下,少的在上;二是实现复原。
  • 发现步骤二很容易,问题是步骤一,如果当前只有一根空柱,似乎难以实现颜色排序,如果有两根空柱的话,则可以一根对应一种颜色,先全部移走,再按颜色移回,
  • 事实上并不需要两根完全空的柱子,只需要它们剩下的空位数分别对应该柱中两种颜色的数目即可,那么这两根中一根是原本的空柱,另一根任选,把一定量的求先放到空柱上达到上述的目标,最后再还原即可。
  • 复杂度为O(nlog⁡n∗m)O(n\log n*m)O(nlognm),总步数的常数因实现而异。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 55
#define M 410
int a[N][M], c[N];
struct {
	int x, y;
}to[825000];
int ans = 0, n, m;
void mv(int x, int y) {
	a[y][++c[y]] = a[x][c[x]];
	a[x][c[x]--] = 0;
	to[++ans].x = x, to[ans].y = y;
}
void solve(int l, int r) {
	if(l == r) return;
	int mid = (l + r) / 2;
	int i = l, j = mid + 1, k;
	while(i <= mid && j <= r) {
		int s = 0;
		for(k = 1; k <= m; k++) s += (a[i][k] <= mid);
		for(k = 1; k <= m; k++) s += (a[j][k] <= mid);
		int x = i, y = j;
		if(s > m) {
			int t = 0;
			for(k = 1; k <= m; k++) t += (a[i][k] <= mid);
			for(k = 1; k <= t; k++) mv(j, n + 1);
			for(k = m; k; k--) if(a[i][k] > mid) mv(i, n + 1); else mv(i, j);
			for(k = 1; k <= t; k++) mv(j, i);
			for(k = t + 1; k <= m; k++) mv(n + 1, i);
			for(k = t; k; k--) mv(n + 1, j);
			
			int t0 = 0;
			for(k = 1; k <= m; k++) t0 += (a[j][k] <= mid);
			for(k = 1; k <= t0; k++) mv(i, n + 1);
			for(k = m; k; k--) if(a[j][k] > mid) mv(j, n + 1); else mv(j, i);
			for(k = 1; k <= t0; k++) mv(i, j);
			for(k = t0 + 1; k <= m; k++) mv(n + 1, j);
			for(k = t0; k; k--) mv(n + 1, i);
			
			for(k = t + 1; k <= m; k++) mv(i, n + 1);
			for(k = t0 + 1; k <= m; k++) mv(j, n + 1);
			for(k = t + 1; k <= m; k++) mv(j, i);
			while(c[n + 1]) mv(n + 1, j);
			i++;
		}
		else {
			int t = 0;
			for(k = 1; k <= m; k++) t += (a[i][k] > mid);
			for(k = 1; k <= t; k++) mv(j, n + 1);
			for(k = m; k; k--) if(a[i][k] <= mid) mv(i, n + 1); else mv(i, j);
			for(k = 1; k <= t; k++) mv(j, i);
			for(k = t + 1; k <= m; k++) mv(n + 1, i);
			for(k = t; k; k--) mv(n + 1, j);
			
			int t0 = 0;
			for(k = 1; k <= m; k++) t0 += (a[j][k] > mid);
			for(k = 1; k <= t0; k++) mv(i, n + 1);
			for(k = m; k; k--) if(a[j][k] <= mid) mv(j, n + 1); else mv(j, i);
			for(k = 1; k <= t0; k++) mv(i, j);
			for(k = t0 + 1; k <= m; k++) mv(n + 1, j);
			for(k = t0; k; k--) mv(n + 1, i);
			
			for(k = t + 1; k <= m; k++) mv(i, n + 1);
			for(k = t0 + 1; k <= m; k++) mv(j, n + 1);
			for(k = t0 + 1; k <= m; k++) mv(i, j);
			while(c[n + 1]) mv(n + 1, i);
			j++;
		}
	}
	solve(l, mid), solve(mid + 1, r);
}
int main() {
	int i, j, k, l;
	scanf("%d%d", &n, &m);
	for(i = 1; i <= n; i++) 
		for(j = 1; j <= m; j++) scanf("%d", &a[i][j]);
	for(i = 1; i <= n; i++) c[i] = m;
	solve(1, n);
	printf("%d\n", ans);
	for(i = 1; i <= ans; i++) printf("%d %d\n", to[i].x, to[i].y);
	return 0;
}

自我小结

  • 这种题手玩可能对正解帮助不大,尽管有一套很好的人工操作策略,用于计算机可能完全不行。更应该考虑比较适合计算机运算的简单重复的过程。
  • 思维要变通,正解不行时要有意识地考虑更劣的做法。
  • 就这题而言,如果能不考虑整段操作,而考虑单个操作,可以轻松得到40分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值