1. 程式人生 > 其它 >[NOIP2020] 移球遊戲

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

程式碼