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

NOIP 2020 T3 移球游戏

题解

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

自我小结

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