题解 P7115 【移球游戏(洛谷民间数据)】
此题解纪念我在考场上通过了本题
考虑先枚举颜色,然后将此颜色的所有球移动到同一个柱子上
Step. 0 问题的转换
假设原来是这么一组数据:
为了方便,我们将每个柱子看作一列,从左到右标号为
记
记
当
这时问题的目标就转化为了将所有的
Step. 1 制造全 0 列
考虑对于每个颜色先制造出一个全
至于为什么要先制造全
-
还是为了方便,我们强制第
n+1 列是空的(没有任何球)具体操作时可以维护一个数组
p ,其中p_i 表示当前第i 列的柱子在最开始时的编号为p_i 这样就可以通过
swap
p 中的两个数来使柱子的排列方便我们构造 -
设
tot=t_1 ,将第n 列最上面的tot 个球移动到第n+1 列(这里用的还是
Step. 0
中的例子) -
将第
1 列中的1 全部移动到第n 列,0 全部移动到第n+1 列具体过程就是如果第一列的最上面为
1 就移到第n 列,否则移到第n+1 列此时可以发现第一列中的
0 和1 被我们分离了 -
将第
n+1 列中的m-tot 个0 全部移回第1 列(其中tot 为第一步中的t_1 ) -
将第
2 列中的0 分离到第1 列,1 分离到第n+1 列(如果第一列塞不下0 了就往第n+1 列丢)此时我们就构造出了一个全
0 列!至于第四步中为什么
0 的数量一定够,是因为前两列中1 的个数一定不大于m ,所以0 的个数一定\geq 2m-1的数量 ,即\geq m 。而这四步的本质也就是将前两列中的0 全部分离到第一列中,所以一定能构造出一个全0 列
Step. 1
实际上就是用第
Step. 2 构造全 1 列
我们在 Step. 1
中构造全
依然是为了方便,我们强制在构造全
所以在构造了全
然后自然是通过全
-
开始的时候图长这样
-
像
Step. 1
中的 1.2. 那样将第1 列分解到第n,n+1 列
然后你就会发现第
所以我们第一步中构造全
然后你又能发现你可以用第
所有操作完成后,所有的
然后我们发现当前枚举的颜色已经合法,剩下的相当于要将
Step. 3 n=2
也许你以为本题的做法到这里就结束了?其实并没有
你会发现这个做法根本过不了样例
因为当
所以要特判
这个应该挺好做的,这里简单介绍一下我的做法:
-
假设开始的图是这样的:
-
还是像
Step. 1
中的 1.2. 那样将第1 列分解到第2,3 列 -
然后再将分离后的丢回第
1 列(先1 后2 )这两步操作相当于将第一列排了个序
-
将第三列移回第二列,然后将第一列上面的
2 移到第三列 -
然后类似
Step. 1
中的 1.2. 那样将第2 列分解到第1,3 列,就能得到全1 列和全2 列了
Step. 4 操作次数分析
最外层枚举颜色一个
因为列数随着颜色一个一个处理完会减小,所以
构造全
复杂度
Step. 5 一些小细节
- 所有对第
i 列的操作实际上都是对p_i 进行的,因为上面说第i 列只是方便思考,实际上时对p_i 进行的操作 - 每次移完球需要动态更新
p 数组,否则会惨烈爆蛋 - 祝大家省选 rp++!
#include<bits/stdc++.h>
using namespace std;
const int N=55;
const int M=405;
const int OPT=820005;
int read()
{
int s=0;
char c=getchar(),lc='+';
while (c<'0'||'9'<c) lc=c,c=getchar();
while ('0'<=c&&c<='9') s=s*10+c-'0',c=getchar();
return lc=='-'?-s:s;
}
void write(int x)
{
if (x<0)
{
putchar('-');
x=-x;
}
if (x<10) putchar(x+'0');
else
{
write(x/10);
putchar(x%10+'0');
}
}
void print(int x=-1,char c='\n')
{
write(x);
putchar(c);
}
int L[OPT],R[OPT],CNT=0,n,m;
int a[N][M],cnt[N],tot[N],p[N];
void move(int x,int y)
{
++CNT;
L[CNT]=x;
R[CNT]=y;
a[y][++cnt[y]]=a[x][cnt[x]--];
}
int count(int x,int y)
{
int ret=0;
for (int i=1;i<=m;i++) ret+=a[x][i]==y;
return ret;
}
inline int top(int x)
{
return a[x][cnt[x]];
}
int main()
{
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
n=read();
m=read();
for (int i=1;i<=n;i++)
{
cnt[i]=m;
for (int j=1;j<=m;j++) a[i][j]=read();
}
cnt[n+1]=0;
for (int i=1;i<=n+1;i++) p[i]=i;
//empty:n+1
//full :n
for (int now=n;now>=3;now--)
{
int tmp=count(p[1],now);
for (int i=1;i<=tmp;i++) move(p[now],p[now+1]);
for (int i=1;i<=m;i++)
if (top(p[1])==now) move(p[1],p[now]);
else move(p[1],p[now+1]);
for (int i=1;i<=m-tmp;i++) move(p[now+1],p[1]);
for (int i=1;i<=m;i++)
if (top(p[2])==now||cnt[p[1]]==m) move(p[2],p[now+1]);
else move(p[2],p[1]);
//构造全 0 列
swap(p[1],p[now]);
swap(p[2],p[now+1]);
for (int k=1;k<now;k++)
{
tmp=count(p[k],now);
for (int i=1;i<=tmp;i++) move(p[now],p[now+1]);
for (int i=1;i<=m;i++)
if (top(p[k])==now) move(p[k],p[now]);
else move(p[k],p[now+1]);
swap(p[k],p[now+1]);
swap(p[k],p[now]);
}
for (int i=1;i<now;i++) while (top(p[i])==now) move(p[i],p[now+1]);
for (int i=1;i<now;i++) while (cnt[p[i]]<m) move(p[now],p[i]);
//构造全 1 列
}
int tmp=count(p[1],1);
for (int i=1;i<=tmp;i++) move(p[2],p[3]);
for (int i=1;i<=m;i++)
if (top(p[1])==1) move(p[1],p[2]);
else move(p[1],p[3]);
for (int i=1;i<=tmp;i++) move(p[2],p[1]);
for (int i=1;i<=m-tmp;i++) move(p[3],p[1]);
while (cnt[p[3]]) move(p[3],p[2]);
for (int i=1;i<=m-tmp;i++) move(p[1],p[3]);
for (int i=1;i<=m;i++)
if (top(p[2])==1) move(p[2],p[1]);
else move(p[2],p[3]);
//特判 n=2
print(CNT);
for (int i=1;i<=CNT;i++)
{
print(L[i],' ');
print(R[i]);
}
return 0;
}