[NOIP2020] 移球游戏
tag:构造
鸽了一万年的题目
显然这是一个不用任何高级算法的纯构造题,多造几个数据手玩一下,大概可以找到一种通解。
首先大体思路是挨个处理每个颜色,处理好一个颜色就扔到最后面去,然后n--
。
对于一个颜色 \(c\),可以分为几步:
下面假设颜色 \(c\) 为 \(1\),而非 \(c\) 的颜色均为 \(0\)。
- 处理出一个全 \(0\) 列,放在第 \(n\) 列。(第 \(n+1\) 列为空列)
- 利用这个全 \(0\) 列和空列将前 \(n-1\) 列中的 \(1\) 全部放到末尾。
- 将前 \(n-1\) 列中的 \(1\) 放到空列,并用第 \(n\) 列去填补空缺。
一些定义
- \(S0\),某一列 \(0\) 的个数
- \(S1\),某一列 \(1\) 的个数
step 1
制造全 \(0\) 列。
- 从 \(n\) 移动 \(S1_1\) 个元素到 \(n+1\)。
- 依次取出第 \(1\) 列每个元素,为 \(1\) 就放到 \(n\),为 \(0\) 就放到 \(n+1\)。(不难发现第一步是在帮第二步让位置)
- 将第 \(n+1\) 列末尾的 \(0\) (刚才移动过来的)全部移动到第 \(1\) 列。
这个时候,第 \(1\) 列会有若干 \(0\),但显然不满一列,所以再从第 \(2\) 列拿一点 \(0\) 过来就行。
- 依次取出第 \(2\) 列,若第 \(1\) 列不满且当前元素为 \(0\),就扔到 \(1\),否则扔到 \(n+1\)。
// make one column all 0
int num1=0;
for(register int i=1; i<=m; i++) if(val(1,i)==n) num1++;
if(num1){
move(n,n+1,num1);
for(register int i=m; i; i--)
if(val(1,i)==n) move(1,n);
else move(1,n+1);
move(n+1,1,m-num1);
for(register int i=m; i; i--)
if(val(2,i)!=n and num1) move(2,1), num1--;
else move(2,n+1);
swap(p[2],p[n+1]); swap(p[1],p[n]);
}
else swap(p[1],p[n]);
这样以后,第一列为全 \(0\) 列,第二列为空列。
简单处理一下,将全 \(0\) 列放到第 \(n\) 列,空列放在第 \(n+1\) 列。(并不需要花费任何操作,只是交换一下编号就行,方便实现和解释)
step 2
将每一列的 \(1\) 放到末尾。
这里假设每一次,第 \(n\) 列都为全 \(0\),而第 \(n+1\) 列为空
- 从 \(n\) 移动 \(S1_i\) 个元素到 \(n+1\)。(还是让位置)
- 依次取出第 \(i\) 列的每个元素,\(1\) 扔到 \(n\),\(0\) 扔到 \(n+1\)。
操作完后,第 \(i\) 列变成空列,第 \(n\) 列为先一堆 \(0\) 再加上一堆 \(1\),第 \(n+1\) 列为全 \(0\) 列。再交换一下编号,使得第 \(n\) 列都为全 \(0\),而第 \(n+1\) 列为空。
// make all 1 on the top of each column
for(register int i=1; i<n; i++){
num1 = 0;
for(register int j=1; j<=m; j++) if(val(i,j)==n) num1++;
if(!num1) continue;
move(n,n+1,num1);
for(register int j=m; j; j--)
if(val(i,j)==n) move(i,n);
else move(i,n+1);
swap(p[i],p[n+1]); swap(p[i],p[n]);
}
操作完每一列以后,前 \(n-1\) 列均为 \(0\cdots01\cdots1\) 的形式,第 \(n\) 列为全 \(0\),第 \(n+1\) 列为空。
step 3
集中前 \(n-1\) 列中的 \(1\)。这一步直接把每一列末尾的 \(1\) 扔到 \(n+1\) 就行了,形成的空缺用全 \(0\) 列去填。
// move all 1 to one column
for(register int i=1; i<n; i++){
num1 = 0;
for(register int j=1; j<=m; j++) if(val(i,j)==n) num1++;
move(i,n+1,num1); move(n,i,num1);
}
n--;
最后可以得到第 \(n\) 列为空,而第 \(n+1\) 列全 \(1\)。
step 4
写完程序后,你会发现样例都过不了……
仔细想想,step 1
要用到三个任意列,一个空列,才能构造出一个全 \(0\) 列。但是当 \(n=2\) 的时候呢?
所以只剩两列的时候单独处理。
- \(2\) 移动 \(S1_1\) 个元素到 \(3\)。(让位置)
- 依次取出 \(1\) 的元素,\(1\) 扔到 \(2\),\(0\) 扔到 \(3\)。
- 把 \(3\) 末尾刚刚扔进去的 \(0\) 放回 \(1\),再把 \(2\) 末尾的 \(1\) 放回 \(1\),再把 \(3\) 全部扔回 \(2\)。
这个时候相当于给第一列排了个序,第二列不变。然后我们把第一列末尾的 \(1\) 放到第三列,那么第一列全 \(0\),第三列全 \(1\)。所以第二列的元素挨个对应着扔就行了。
// solve last 2 columns
int num1=0;
for(register int i=1; i<=m; i++) if(val(1,i)==1) num1++;
if(num1!=m and num1!=0){
//sort column 1
move(2,3,num1);
for(register int i=m; i; i--)
if(val(1,i)==1) move(1,2);
else move(1,3);
move(2,1,num1); move(3,1,m-num1); move(3,2,num1);
//deal with column 2
move(1,3,m-num1);
for(register int i=m; i; i--)
if(val(2,i)==1) move(2,1);
else move(2,3);
}
最后的样子应该是第一列全 \(1\),第二列空,第 \(i\) 列全为 \(i-1\)。(\(i\geq3\))