[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\)

代码

posted @ 2021-06-26 14:04  oisdoaiu  阅读(89)  评论(0编辑  收藏  举报