NOIP2020 T3移球游戏
有\(n+1\)个栈,一开始前\(n\)个栈中每个栈各有\(m\)个数。每种数的出现次数为\(n\)。
每次将一个栈的栈顶取出丢到另一个栈的栈顶。要求操作之后栈大小不超过\(m\)。
要求每个栈中元素相同。
\(n\le 50,m\le 400\)
操作次数限制\(820000\)。
构造白痴表示连40分都不会。
讲正解:
考虑\(n=2\)的情况,分两步:将每个栈调整为上面白下面黑,然后再随便搞搞。
设栈\(A\)黑色有\(x\)个。如此操作:
- 取\(B\)中\(x\)个数丢入\(C\)中。
- 取\(A\)中的所有数,如果是黑色丢入\(B\)中,否则丢入\(C\)中。
- 将\(B\)中\(x\)个数(黑色)丢入\(A\)中,\(C\)中\(m-x\)个数丢入\(A\)中。
可以发现,这样之后\(A\)被成功调整,而其它的没有变化。
考虑\(n>2\)。可以分治做,黑白染色后做上述操作。
搞完之后需要把栈变成全黑或全白。可以这样搞:随便取出两个栈,记\(x,y\)分别为它们的黑色出现次数。如果\(x+y\ge m\),将\(A\)和\(B\)上的白色都丢入\(C\)中,把\(B\)中的黑色丢入\(A\)中,再将白色放回来;如果\(x+y<m\),把\(A\)全部丢入\(C\)中,把\(B\)的白色丢入\(A\)中,把\(C\)中的黑色丢到\(B\)中,剩下的白色丢到\(A,B\)中。
发现复杂度是\(O(nm\lg n)\)的,可以过。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cassert>
#define N 55
#define M 405
int n,m;
int a[N][M],c[N];
pair<int,int> o[1000000];
int oc;
void opt(int x,int y){
o[++oc]={x,y};
assert(c[y]<m);
assert(c[x]>0);
a[y][++c[y]]=a[x][c[x]--];
}
int cnt(int i,int mid){
assert(c[i]==m);
int s=0;
for (int j=1;j<=m;++j)
s+=(a[i][j]<=mid);
return s;
}
void divide(vector<int> p,int l,int r){
if (p.size()==1)
return;
int mid=l+r>>1;
for (int ii=0;ii<p.size();++ii){
int i=p[ii],t=(i==1?n:1),x=cnt(i,mid);
for (int j=1;j<=x;++j)
opt(t,n+1);
for (int j=m;j>=1;--j)
if (a[i][j]<=mid)
opt(i,t);
else
opt(i,n+1);
for (int j=1;j<=x;++j)
opt(t,i);
for (int j=1;j<=m-x;++j)
opt(n+1,i);
for (int j=1;j<=x;++j)
opt(n+1,t);
}
vector<int> lp,rp;
lp.clear(),rp.clear();
for (int ii=0;ii<p.size();++ii){
int i=p[ii],x=cnt(i,mid);
if (x==m)
lp.push_back(i);
else if (x==0)
rp.push_back(i);
else{
int t=p[ii+1],y=cnt(t,mid);
if (x+y>=m){
for (int j=1;j<=m-x;++j)
opt(i,n+1);
for (int j=1;j<=m-y;++j)
opt(t,n+1);
for (int j=1;j<=m-x;++j)
opt(t,i);
for (int j=1;j<=m-y+m-x;++j)
opt(n+1,t);
lp.push_back(i);
}
else{
for (int j=1;j<=m;++j)
opt(i,n+1);
for (int j=1;j<=m-y;++j)
opt(t,i);
for (int j=1;j<=x;++j)
opt(n+1,t);
for (int j=1;j<=y;++j)
opt(n+1,i);
for (int j=1;j<=m-x-y;++j)
opt(n+1,t);
rp.push_back(i);
}
}
}
// for (int i=0;i<lp.size();++i)
// for (int j=1;j<=m;++j)
// assert(a[lp[i]][j]<=mid);
divide(lp,l,mid);
divide(rp,mid+1,r);
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i){
for (int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
c[i]=m;
}
vector<int> p;
p.clear();
for (int i=1;i<=n;++i)
p.push_back(i);
divide(p,1,n);
printf("%d\n",oc);
for (int i=1;i<=oc;++i)
printf("%d %d\n",o[i].first,o[i].second);
return 0;
}