這道題講道理還是不錯的,因為你需要不斷挖掘其中的性質來幫助解題。可惜數據范圍開在這里讓考試時的我很慌,勉強也就寫了$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; }
這份代碼是$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; }
$\bigodot$總結:
對於這道題的我的做法,或許與大多數做法不一定相同,它並沒有要求什么算法,甚至沒有類可歸,重要的是要細心耐心地思考與推導。