P7115 移球游戏
https://www.luogu.com.cn/problem/P7115
考场上做出了70分做法
先考虑能把一个柱子上的球做什么样的操作,有一种想法是把某一种颜色的球全部放到顶部
具体做法大概是这样,先选择任意一个满栈 \(O\),和一个空栈 \(E\),操作:
- 记这个要操作的栈为 \(X\),设 \(X\) 中有 \(num\) 个这种颜色的球
- 把 \(H\) 中最上面的 \(num\) 个放入 \(E\) 中
- 对于 \(X\),把所有是这种颜色的球放入 \(O\) 中,不是的放入 \(E\) 中。此时 \(X\) 空,\(E,O\) 均已满
- 把 \(E\) 中最上面的 \(m-num\) 个放回 \(X\) 中,再把 \(O\) 中最上面的 \(num\) 个放回 \(X\)。此时对于 \(X\) 的操作已完成
- 把 \(E\) 中剩下的 \(num\) 个放回 \(O\),此时 \(O\) 已经恢复原状,\(E\) 空
做这样一次操作的操作次数是 \(2m+2num\)
由此,一种比较显然的做法就是,枚举每种颜色,把每个混乱(就是不是单色)的柱子中的这种颜色都放到顶部,然后再把他们全都提取到空栈中,就又多了一个不混乱的柱子
然后继续枚举下一个颜色,直到所有柱子都不混乱
在卡一卡操作次数的常数,计算的可以过 70 分(实际上ccf数据过了80分)
对于正解,采用分治的方法
对于颜色区间 \([l,r]\),把所有 \([l,mid]\) 的颜色视为同一种颜色,\([mid+1,r]\) 也视为同一种
然后把 \([l,mid]\) 的提升到对应柱子的顶部,然后对于每两个柱子,我们总是能令其中的一个中的所有颜色都变成 \([l,mid]\) 或 \([mid+1,r]\)
把每个柱子都选择另一个柱子来这样做就可以了
这样,有 \(mid-l+1\) 个柱子变成了颜色 \([l,mid]\),另外 \(r-mid\) 个变成了 \([mid+1,r]\),再进行递归即可
当 \(l=r\) 时,也就完成了操作,当前柱子上的颜色统一了
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define reg register
inline int read(){
register int x=0;register int y=1;
register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')y=0;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-48;c=getchar();}
return y?x:-x;
}
int n,m;
int stack[55][455],top[55];
int A[820006],B[820006],k;
inline void putt(int a,int b){
// printf("%d %d\n",a,b);
A[++k]=a;B[k]=b;
stack[b][++top[b]]=stack[a][top[a]];
top[a]--;
}
inline int cnt(int i,int p){
int num=0;
for(reg int o=1;o<=m;o++) num+=(stack[i][o]<=p);
return num;
}
void work(int l,int r,int col[]){
if(l==r) return;
int mid=(l+r)>>1;
for(reg int i=1;i<=r-l+1;i++){
int other=i+1;
if(other==r-l+2) other=1;
int backup=i;
i=col[i];other=col[other];
int num=cnt(i,mid),dibu;
if(!num) continue;
for(reg int o=1;o<=num;o++) putt(other,n+1);
for(reg int o=1;o<=m;o++){
if(stack[i][o]<=mid){dibu=o;break;}
}
for(reg int o=m;o>=dibu;o--){
if(stack[i][o]<=mid) putt(i,other);
else putt(i,n+1);
}
for(reg int o=dibu;o<=m-num;o++) putt(n+1,i);
for(reg int o=m-num+1;o<=m;o++) putt(other,i);
for(reg int o=m-num+1;o<=m;o++) putt(n+1,other);
i=backup;
}//将所有 [l,mid] 颜色置于每个柱子的顶部
int col1[52],col2[52];
col1[0]=col2[0]=0;
for(reg int i=1;i<=r-l;i++){
int backup=i;
int i1=col[i+1];i=col[i];
int num1=cnt(i,mid),num2=cnt(i1,mid);
if(num1+num2>=m){
col1[++col1[0]]=i;
for(reg int o=m;o;o--) putt(i,n+1);
for(reg int o=m;stack[i1][o]<=mid&&o;o--) putt(i1,i);
for(;top[i1]<m;) putt(n+1,i1);
for(;top[i]<m;) putt(n+1,i);
}
else{
col2[++col2[0]]=i;
for(reg int o=m;stack[i][o]<=mid&&o;o--) putt(i,n+1);
for(reg int o=m;stack[i1][o]<=mid&&o;o--) putt(i1,n+1);
for(;top[i]<m;) putt(i1,i);
for(;top[i1]<m;) putt(n+1,i1);
}
i=backup;
}
if(stack[col[r-l+1]][1]<=mid) col1[++col1[0]]=col[r-l+1];
else col2[++col2[0]]=col[r-l+1];
work(l,mid,col1);work(mid+1,r,col2);
}
int main(){
n=read();m=read();
for(reg int i=1;i<=n;i++){
for(reg int j=1;j<=m;j++) stack[i][j]=read();
top[i]=m;
}
int col[52];
for(reg int i=1;i<=n;i++) col[i]=i;
work(1,n,col);
printf("%d\n",k);
for(reg int i=1;i<=k;i++) printf("%d %d\n",A[i],B[i]);
return 0;
}