NOIP2020 移球游戏
Solution
考虑一个基本操作,如果有两个柱子 \(x,y\) 是满的,\(z\) 是空的,这 \(2m\) 个球中有 \(m\) 个关键球,要把所有的关键球移到同一根柱子上。
设 \(x\) 柱上有 \(a\) 个关键球,操作如下:
(1)把 \(y\) 柱顶部的 \(a\) 个球移到 \(z\) 柱。
(2)把 \(x\) 柱上的球依次移走,如果是关键球就移到 \(y\) 柱,否则移到 \(z\) 柱。
(3)把 \(z\) 柱顶部的 \(m-a\) 个球移回 \(x\) 柱,再把 \(y\) 柱顶部的 \(a\) 个球移回 \(x\) 柱。
(4)把 \(x\) 柱顶部的 \(a\) 个关键球移到 \(z\) 柱。
(5)把 \(y\) 柱上的球依次移走,如果是关键球就移到 \(z\) 柱,否则移到 \(x\) 柱。
这样结束之后,所有的关键球都到了 \(z\) 柱上。
这样 \(n=2\) 就做完了,\(O(m)\)。
回到原问题,考虑分治:定义分治函数 \(solve(l,r)\),每次定 \(mid=\lfloor\frac{l+r}2\rfloor\),把 \(\le mid\) 和 \(>mid\) 的球分开,然后进行 \(solve(l,mid)\) 和 \(solve(mid+1,r)\)。
如何分开,可以取出这 \(r-l+1\) 根柱子,每次找两根柱子,如果这两根柱子中 \(\le mid\) 的球比 \(>mid\) 的球多,就选 \(m\) 个 \(\le mid\) 的球作为关键球,否则选 \(m\) 个 \(>mid\) 的球作为关键球,进行一轮以上基本操作,这样就制造出了一个满足所有球都在 \([l,mid]\) 内或所有球都在 \([mid+1,r]\) 内的柱子。
经过 \(r-l\) 轮,所有 \(r-l+1\) 根柱子都满足这个条件,可以递归下去。
操作次数 \(O(nm\log n)\)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 55, M = 405, L = 888888;
int n, m, tot, X[L], Y[L], sze[N], a[N][M], em;
bool isx[M], isy[M], vis[N];
void record(int x, int y)
{
a[y][++sze[y]] = a[x][sze[x]]; sze[x]--;
X[++tot] = x; Y[tot] = y;
}
int mer(int x, int y, int mid)
{
int cntl = 0, cntr = 0;
memset(isx, 0, sizeof(isx)); memset(isy, 0, sizeof(isy));
for (int i = 1; i <= m; i++) isx[i] = a[x][i] <= mid,
isy[i] = a[y][i] <= mid, cntl += isx[i], cntr += isy[i];
if (cntl + cntr > m)
{
cntl = m - cntl; cntr = m - cntr;
for (int i = 1; i <= m; i++) isx[i] ^= 1, isy[i] ^= 1;
}
for (int i = 1; i <= m; i++) if (!isx[i] && cntl + cntr < m)
cntl++, isx[i] = 1;
for (int i = 1; i <= cntl; i++) record(y, em);
for (int i = m; i >= 1; i--) record(x, isx[i] ? y : em);
for (int i = 1; i <= m - cntl; i++) record(em, x);
for (int i = 1; i <= cntl; i++) record(y, x);
for (int i = 1; i <= cntl; i++) record(em, y);
for (int i = 1; i <= cntl; i++) record(x, em);
for (int i = m; i >= 1; i--) record(y, isy[i] ? em : x);
int res = em;
return em = y, res;
}
void solve(int l, int r)
{
if (l == r) return;
int mid = l + r >> 1;
std::vector<int> orz;
for (int i = 1; i <= n + 1; i++)
if (sze[i] && l <= a[i][1] && a[i][1] <= r)
orz.push_back(i);
for (int i = 0; i + 1 < orz.size(); i++)
orz[i + 1] = mer(orz[i], orz[i + 1], mid);
solve(l, mid); solve(mid + 1, r);
}
int main()
{
#ifdef ONLINE_JUDGE
freopen("ball.in", "r", stdin);
freopen("ball.out", "w", stdout);
#endif
read(n); read(m); em = n + 1;
for (int i = 1; i <= n; i++)
{
sze[i] = m;
for (int j = 1; j <= m; j++) read(a[i][j]);
}
solve(1, n);
std::cout << tot << std::endl;
for (int i = 1; i <= tot; i++) printf("%d %d\n", X[i], Y[i]);
return 0;
}