• 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分。
  • 相关阅读:
    【转】Android之Inflate()方法用途+setContentView和inflate区别
    【转】Android 面试题笔记android五种布局及其作用
    温故知新系列(一)冒泡排序
    一道Java面试题目——随想+心得
    关于String 和 StringBuffer、StringBuilder
    Mysql变量
    Mysql函数
    Mysql流程控制
    Mysql事物
    Mysql视图
  • 原文地址:https://www.cnblogs.com/LZA119/p/14279492.html
Copyright © 2020-2023  润新知