P7115 [NOIP2020] 移球遊戲
給定 \(n+1\) 個柱子,其中有一個空柱,其餘 \(n\) 個上分別有 \(m\) 個球,球分為 \(n\) 種顏色,每種顏色各 \(m\) 個。
可以在柱子間不斷移動球,一個柱子最多同時存在 \(m\) 個球。
要求構造一種移球方案,使得同種顏色球移動到同一根柱子,步數 \(\leq 820000\)。
先想簡單情況,這是所有構造題的基本思路來源。
假設有 \(2\) 個柱子,球的顏色為 \(0/1\),結合一個空柱,如何將柱子變為上面一段是 \(0\) 下面一段是 \(1\) 的狀態。
首先顯然排序一個柱子需要結合另一個非空柱,因為僅僅一個空柱只能將整個顏色串倒序而已。
假設需要排序的柱子中有 \(a\)
那麼只需要將那個非空柱的 \(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; }