P7115
[NOIP2020] 移球游戏
挺考验思维的一道构造题,而且相当有趣。
抽象一个上提或下压的过程:
假设我们有一个目标柱
那么我们假设只有两种颜色 0 或 1:把 0 全部弄到
那么我们先讨论上提:
对于柱
-
将
Y 上方的a 个球依次移动至E ; -
将
X 柱上的球依次取出,若颜色为 0 则放入Y ,反之放入E 。 -
将
X 取空后,Y 与E 恰好满了。 -
将
E 上方m-a 个球依次移动至X ,再将Y 上方a 个球依次移动至X 。 -
最后将
E 中的a 个球依次移动至Y 。
此时,
下压是同理,只要将第 4 步的顺序调换一下即可。
操作步骤数是
显然这种做法可以针对某一种单一颜色,依次将每个柱上的该颜色上提,并全部移动到一个柱子上,但是该做法的步骤数达到
考虑一种分治的做法。
我们定义一个函数
定义
现在的问题就是如何调整。
显然我们可以把颜色
进行一个类似归并的过程,使用两个指针
我们可以针对所指的两个柱,将颜色 0 尽量移动到柱
现在我们可以思考填充的方法。
先讨论填充柱
有了上提和下压的方法,我们可以先把柱
同理,我们用类似的方法也能完成柱
这样我们就可以使区间在函数执行前满足条件了。
那么这个问题就已经解决了。
操作次数的话,我们单次调整柱
那么可以主定理计算步骤数:
常数略大但是无所谓,已经可以通过该题了。
至于一些卡常的技巧,可以在下压和上提的过程中不把
然后针对这种方法应该没有更好地卡常技巧了(因为严格界定了颜色与区间的关系,柱与柱之间的相对关系没有办法转换)。
代码略长,不要彷徨。
代码:
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=50,M=4e2,S=9e5;
ll n,m,step;
ll top[N+5],st[N+5][M+5],ansx[S+5],ansy[S+5];
void remove(ll u,ll v) {
st[v][++top[v]]=st[u][top[u]--];
ansx[++step]=u;ansy[step]=v;
}
void download(ll x,ll h,ll mid) {
ll a=0;
for(ll i=1;i<=m;i++) a+=(st[x][i]<=mid);
for(ll i=1;i<=a;i++) remove(h,n+1);
for(ll i=1;i<=m;i++) {
if(st[x][top[x]]<=mid) remove(x,h);
else remove(x,n+1);
}
for(ll i=1;i<=a;i++) remove(h,x);
for(ll i=1;i<=m-a;i++) remove(n+1,x);
for(ll i=1;i<=a;i++) remove(n+1,h);
}
void upload(ll x,ll h,ll mid) {
ll a=0;
for(ll i=1;i<=m;i++) a+=(st[x][i]>mid);
for(ll i=1;i<=a;i++) remove(h,n+1);
for(ll i=1;i<=m;i++) {
if(st[x][top[x]]>mid) remove(x,h);
else remove(x,n+1);
}
for(ll i=1;i<=a;i++) remove(h,x);
for(ll i=1;i<=m-a;i++) remove(n+1,x);
for(ll i=1;i<=a;i++) remove(n+1,h);
}
void filldown(ll x,ll y,ll mid) {
ll a=0;
for(ll i=1;i<=m;i++) a+=(st[x][i]>mid);
for(ll i=1;i<=a;i++) remove(x,n+1);
for(ll i=1;i<=a;i++) remove(y,x);
for(ll i=1;i<=a;i++) remove(n+1,y);
}
void fillup(ll x,ll y,ll mid) {
ll a=0;
for(ll i=1;i<=m;i++) a+=(st[x][i]<=mid);
for(ll i=1;i<=a;i++) remove(x,n+1);
for(ll i=1;i<=a;i++) remove(y,x);
for(ll i=1;i<=a;i++) remove(n+1,y);
}
bool merge(ll x,ll y,ll mid) {
ll cnt_0=0,cnt_1=0;
for(ll i=1;i<=m;i++) {
if(st[x][i]<=mid) cnt_0++;
else cnt_1++;
if(st[y][i]<=mid) cnt_0++;
else cnt_1++;
}
download(x,y,mid);upload(y,x,mid);
if(cnt_0>=cnt_1) filldown(x,y,mid);
else fillup(y,x,mid);
return cnt_1>cnt_0;
}
void solve(ll l,ll r) {
if(l==r) return;
ll mid=(l+r)>>1;
ll i=l,j=mid+1,tmp=0;
while(i<=mid&&j<=r) {
tmp=merge(i,j,mid);
if(tmp) j++;
else i++;
}
solve(l,mid);solve(mid+1,r);
}
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();}
return ret*f;
}
void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {
do{buf[++len]=x%10+48;x/=10;}while(x);
}
else {
putchar('-');
do{buf[++len]=-(x%10)+48;x/=10;}while(x);
}
while(len>=0) putchar(buf[len--]);
}
void writeln(ll x) {
write(x);putchar('\n');
}
void print() {
writeln(step);
for(ll i=1;i<=step;i++) {
write(ansx[i]);putchar(' ');writeln(ansy[i]);
}
}
int main() {
n=read();m=read();
for(ll i=1;i<=n;i++) {
top[i]=m;
for(ll j=1;j<=m;j++) {
st[i][j]=read();
}
}
solve(1,n);
print();
return 0;
}