1. 程式人生 > 其它 >P7115 [NOIP2020] 移球遊戲

P7115 [NOIP2020] 移球遊戲

構造題

移球遊戲

給定 \(n+1\) 個柱子,其中有一個空柱,其餘 \(n\) 個上分別有 \(m\) 個球,球分為 \(n\) 種顏色,每種顏色各 \(m\) 個。

可以在柱子間不斷移動球,一個柱子最多同時存在 \(m\) 個球。

要求構造一種移球方案,使得同種顏色球移動到同一根柱子,步數 \(\leq 820000\)

先想簡單情況,這是所有構造題的基本思路來源。

假設有 \(2\) 個柱子,球的顏色為 \(0/1\),結合一個空柱,如何將柱子變為上面一段是 \(0\) 下面一段是 \(1\) 的狀態。

首先顯然排序一個柱子需要結合另一個非空柱,因為僅僅一個空柱只能將整個顏色串倒序而已。

假設需要排序的柱子中有 \(a\)

\(0\) 號球。

那麼只需要將那個非空柱的 \(a\) 個球移動到空柱,然後將 \(a\) 中的 \(0\) 移動到它,\(1\) 移動到空柱。

最後在先將 \(1\) 放回來,後將 \(0\) 放回來即可,最後將非空柱還原。

那麼總運算元最大為 \(4m\),可以卡個小常數,每次將 \(0/1\) 中較少個的作為 \(a\),總數變為 \(3m\)

針對兩種顏色,都排序之後簡單討論一下就能得到答案。

考慮擴充套件到多種顏色,一個關鍵思想是利用分治,每次將 \(\leq mid\) 的顏色當做 \(0\)\(>mid\) 的當做 \(1\)

這樣每次將 \(0/1\) 分離後,分治下去即可得到答案。

分離的方法是,先將所有 \(n\)​ 個柱子排序,然後每次找到兩個柱子,使它們的 \(0/1\)​ 加起來 \(\geq m\)​。

簡單討論一下,自然可以在 \(3m\) 的步數內,得到一個全 \(0/1\) 柱,這個操作只需要做 \(n/2\) 次。

那麼總次數就是 \(4.5nm\log n<5nm\log n\)​​ 的步數內得到答案,計算髮現遠小於 \(820000\)

實現思路清晰其實也不難寫(

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

typedef pair<int, int> PII;
#define MP make_pair
#define PB push_back
#define X first
#define Y second

const int N = 55, M = 410;
int n, m, tot[N], a[N][M], b[N][2];
vector<PII> ans;

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void Add(int u, int v){
	a[v][++ tot[v]] = a[u][tot[u] --];
	ans.PB(MP(u, v));
}

void Move(int u, int v, int x) {while(x --) Add(u, v);}

void Get(int l, int r, vector<int> S){
	if(l == r) return;
	int mid = (l + r) >> 1;

	for(int i = 0; i < (int) S.size(); i ++){
		int u = S[i]; b[u][0] = b[u][1] = 0;
		for(int j = 1; j <= m; j ++) b[u][a[u][j] > mid] ++;
		int v = (u == 1) ? 2 : 1;
		int w = (b[u][0] < b[u][1]) ? 0 : 1;
		Move(v, n + 1, b[u][w]);
		for(int j = m; j >= 1; j --)
			if((a[u][j] > mid) == w) Add(u, v);
			else Add(u, n + 1);
		if(w == 0){
			Move(n + 1, u, b[u][!w]);
			Move(    v, u, b[u][ w]);
		}
		else{
			Move(    v, u, b[u][ w]);
			Move(n + 1, u, b[u][!w]);
		}
		Move(n + 1, v, b[u][w]);
	}
	
	while(true){
		for(int i = 0; i < (int) S.size(); i ++){
			int u = S[i]; b[u][0] = b[u][1] = 0;
			for(int j = 1; j <= m; j ++) b[u][a[u][j] > mid] ++;
		}
		
		int Mx = 0, u = 0, _Mx = 0, v = 0;
		for(int i = 0, w; i < (int) S.size(); i ++) 
			if(b[w = S[i]][0] && b[w][1]){
				if(b[w][0] >= Mx)
					_Mx = Mx, v = u, Mx = b[w][0], u = w;
				else if(b[w][0] > _Mx)
					_Mx = b[w][0], v = w;
			}
		if(Mx + _Mx >= m){
			Move(    v, n + 1, m);
			Move(    u,     v, b[u][0]);
			Move(n + 1,     u, b[v][1]);
			Move(n + 1,     v, m - b[u][0]);
			Move(n + 1,     u, b[u][0] - b[v][1]);
			continue;
		}
		
		Mx = 0, u = 0, _Mx = 0, v = 0;
		for(int i = 0, w; i < (int) S.size(); i ++) 
			if(b[w = S[i]][0] && b[w][1]){
				if(b[w][1] >= Mx)
					_Mx = Mx, v = u, Mx = b[w][1], u = w;
				else if(b[w][1] > _Mx)
					_Mx = b[w][1], v = w;
			}
		if(Mx + _Mx >= m){
			Move(u, n + 1, b[u][0]);
			Move(v, n + 1, b[v][0]);
			Move(v, u, m - b[u][1]);
			Move(n + 1, v, b[u][0] + b[v][0]);
			continue;
		}
		
		break;
	}
	
	vector<int> LS, RS;
	for(int i = 0; i < (int) S.size(); i ++){
		int u = S[i];
		if(a[u][m] <= mid) LS.PB(u);
		else RS.PB(u); 
	}
	Get(      l, mid, LS);
	Get(mid + 1,   r, RS);
}

int main(){
	n = read(), m = read();
	vector<int> S;
	for(int i = 1; i <= n; i ++){
		tot[i] = m; S.PB(i); 
		for(int j = 1; j <= m; j ++) a[i][j] = read();
	}

	Get(1, n, S);

	printf("%d\n", ans.size());
	for(int i = 0; i < (int) ans.size(); i ++)
		printf("%d %d\n", ans[i].X, ans[i].Y);
	return 0;
}