【NOIP 2018】填數游戲(思考與推導)


題目鏈接

這道題講道理還是不錯的,因為你需要不斷挖掘其中的性質來幫助解題。可惜數據范圍開在這里讓考試時的我很慌,勉強也就寫了$65$分就沒了。回憶在考場上,思路是沒有錯的,就是發掘不夠深入,思路還不夠清晰。事實上考場上沒有選擇繼續做這道題是對的,因為就算是我考后仔細分析之后,寫完這道題仍然花了我不少時間。

我們可以循着思路一步步分析,一步步得到每一個性質。

 題目中對其走過路徑的字典序的比較提示我們按斜行分析。稍加思考我們就能得到一個明顯的結論,就是對於某一個格子如果它是$1$,那它的右上角的那個格子就不能是$0$,這幾乎就是題目條件的定義,因為有一條路徑走到了這個格子,它就會在分叉的時候出問題。我們它這個性質總結一下就能得到我們需要的第一個結論:

1.對於每一個斜行,其$0/1$狀態一定是存在一個分界點,使得其左下方都是$1$,其右上方都是$0$。

有以上的結論將大大減少每一個斜行的可行的$0/1$狀態。我們接着思考,一對不合法的路徑的出現,除了上述的情況,都可以歸結為兩條不同的路徑以相同的$0/1$串走到了某一個格子,但是這個格子右邊下邊的兩個格子的$0/1$是不同的,這同樣會讓矛盾出現。或許你會想這兩條路徑在$0/1$字典序出現分歧的時候並不一定在同一個格子里,但如果存在這種情況,那我們一定能找到前者所說的更加簡單的情況。我們將形式地描述這個問題,我們稱一個格子是“模糊”的,當且僅當存在兩條不同的路徑以相同的$0/1$串走到了這個格子。我們所發現的可以表述成:

2.如果某一個格子它左邊上邊的兩個格子的$0/1$是相同的,或者它左邊或上邊有格子是模糊點,那這個格子就是模糊點;模糊點右邊下邊的兩個格子的$0/1$必須相同。

這也是一個重要的結論,它為下一個結論的得到提供了一個有力的幫助。模糊點的傳遞性隱約讓我們感覺到它們的排布不會錯雜無序,事實上十分有規律。讀者可以仔細推敲,利用歸納法簡單證明以下結論:

3.去除第一行和第一列的格子后,每一條斜行最多只有一個格子是非模糊的,並且這個非模糊點一定在第二行或第二列。

這個性質令人驚訝,但它是真實的,並且不難證明。有了這個結論后,我們就可以有一個大致的想法,我們可以枚舉整張圖的模糊狀態,狀態數是$O(m)$的,因為斜行上一旦全是模糊點,接下來也一定都是模糊點。我們考慮對於一個給定的模糊狀態,我們怎么去計算有多少$0/1$的填放方式滿足整張圖的模糊狀態。假設我們枚舉那個僅存的非模糊點最后出現在哪一個斜行,手模一下可以發現,為了保證這個非模糊點沒有消失,前面的大多數斜行的$0/1$狀態是唯一的,只有最開頭的兩斜行會有多種狀態,並且為了讓這個非模糊點在下一斜行中消失,這行和下一行的可行狀態數也可以知道,那么算到這里方案數還是一個已知的常數。在非模糊點消失后,接下來每一斜行面臨的決策都是一樣的,對於后面的方案數只要快速冪即可。到這里為止,已經可以解決這道題了,利用此算法的復雜度是$O(mlogm)$。

講到這里算法大致結束了。對於上述算法而言,我們枚舉了非模糊點最后出現的位置然后算方案數,其算式的形式是相同的,我們可以把其中的式子化簡一下,就能用等比數列求和直接算了,在此處當$n=m$的時候要求有$3$的逆元。所以,總復雜度為$O(logm)$。這道題的細節相當復雜,其中的有許多常數要手動算出來,也有一些角落需要特判,就算大致知道怎么做了之后實現起來也是不容易的。

 

這份代碼是$O(mlogm)$的實現,寫的時候也是有點邏輯順序在里面的,總的來說是按照斜行的從上到下。可能其中有需要解釋的地方,$C$函數用於求在$x + 1$斜行后全部都是模糊點的方案數的計算,one more case中是一個算非模糊點在第二列最后兩個位置時特判,dd line是非模糊點在第$n$斜行的特判,last case是非模糊點在第二行最后兩個位置時的特判。

#include <cstdio>
#include <algorithm>

typedef long long LL;

const int MOD = (int)1e9 + 7;

int n, m, ans, p2, p3;

int Pow(int x, int b) {
  int r = 1;
  for (; b; b >>= 1, x = (LL)x * x % MOD) if (b & 1) r = (LL)r * x % MOD;
  return r;
}
int C(int x) { // end by x, calc after
  if (x >= m) return Pow(2, n + m - x - 1);
  if (x >= n) return (LL)Pow(3, m - x) * p2 % MOD;
  return (LL)Pow(4, n - x) * p3 % MOD * p2 % MOD;
}

int main() {
  scanf("%d%d", &n, &m);
  if (n > m) std::swap(n, m);
  p2 = Pow(2, n - 1);
  p3 = Pow(3, m - n);
  if (n == 1) {
    printf("%d\n", Pow(2, m));
    return 0;
  }
  if (n == 2) {
    printf("%lld\n", 4LL * Pow(3, m - 1) % MOD);
    return 0;
  }
  ans = (ans + 16LL * C(3)) % MOD; // on line 2
  ans = (ans + (3 + (n != 3) + (m > 3)) * 4LL * C(4)) % MOD; // on line 3
  for (int i = 4; i < n; ++i) {
    ans = (ans + 80LL * C(i + 1)) % MOD;
  }
  // one more case
  if (n > 3) {
    if (n < m) ans = (ans + 32LL * C(n + 1)) % MOD;
    else ans = (ans + 24LL * C(n + 1)) % MOD;
  }
  if (n < m) ans = (ans + 8LL * C(n + 1)) % MOD;
  else ans = (ans + 6LL * C(n + 1)) % MOD;

  // dd line
  if (n < m && n != 3) ans = (ans + 32LL * C(n + 1)) % MOD;
  for (int i = n + 1; i < m; ++i) {
    ans = (ans + 24LL * C(i + 1)) % MOD;
  }
  // last case
  if (n > 3 || m > 3) {
    if (n < m) ans = (ans + 18LL * C(m + 1)) % MOD;
    else ans = (ans + 24LL * C(m + 1)) % MOD;
  }
  ans = (ans + 6LL * C(m + 1)) % MOD;
  
  printf("%d\n", ans);
  return 0;
}
View Code 

這份代碼是$O(logm)$的實現,其中把一些項合並過了,故而稍變簡潔,但是無法從中得到具體的含義的。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;

const int MOD = (int)1e9 + 7;

int n, m, ans, p2, p3;

int Pow(int x, int y) {
  int r = 1, b = (y + MOD - 1) % (MOD - 1);
  for (; b; b >>= 1, x = (LL)x * x % MOD) if (b & 1) r = (LL)r * x % MOD;
  return r;
}

int main() {
  scanf("%d%d", &n, &m);
  if (n > m) swap(n, m);
  p2 = Pow(2, n - 1);
  p3 = Pow(3, m - n);
  if (n == 1) ans = Pow(2, m);
  if (n == 2) ans = 4LL * Pow(3, m - 1) % MOD;
  if (n == 3) ans = 112LL * p3 % MOD;
  if (n > 3) {
    ans = (3LL * p2 + 21LL * Pow(2, 3 * n - 7) % MOD * p3) % MOD;
    if (n == m) ans = (ans + 27LL * p2) % MOD;
    else ans = (ans + (24LL * p3 % MOD + 9) * p2) % MOD;
    ans = (ans + 80LL * p2 % MOD * Pow(3, m - n - 1) % MOD * (Pow(4, n - 4) - 1)) % MOD;
    ans = (ans + 12LL * p2 % MOD * (Pow(3, max(0, m - n - 1)) - 1)) % MOD;
  }
  printf("%d\n", ans);
  return 0;
}
View Code

 

$\bigodot$總結:

對於這道題的我的做法,或許與大多數做法不一定相同,它並沒有要求什么算法,甚至沒有類可歸,重要的是要細心耐心地思考與推導。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM