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
",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
",k);
for(reg int i=1;i<=k;i++) printf("%d %d
",A[i],B[i]);
return 0;
}