Codeforces 506E Mr. Kitayuta's Gift - 動態規劃 - 矩陣
題目傳送門
通往Codeforces的航線
通往vjudge的航線
題目大意
給定一個僅包含小寫字母的串$s$,要求插入恰好$n$個小寫字母字符,使得新串為回文串。問這樣得到的新串有多少個是本質不同回文串。
$1\leqslant |s| \leqslant 200,1 \leqslant n \leqslant 10^{9} $
神題orz...orz...orz。Petr orz...orz...orz。
好了開始扯正題了。
任意位置插入並不太好處理。可以轉化一下這個問題,求一個長度為$|s| + n$的回文串,要求以$|s|$作為其中的一個子序列。
因為是回文串,所以可以從兩邊開始向中間動態規劃,用$f[i][l][r]$表示我已經在半邊加入了$i$個字符,中間還需要存在一個子序列是$s$的$[l, r]$這一段。
轉移枚舉是否是$s_{l}$或者$s_{r}$的字符,如果兩端相等需要特判。
然而這樣轉移$O(|s|^{2}n)$,可以用矩陣快速冪優化成$O(|s|^{6}\log n)$。非常優秀,完美TLE。
考慮這麽轉移的一個過程可以看做一個自動機:
其中紅色的節點有24個自環轉移(自己到自己),綠色節點有25個自環轉移,藍色節點為終止狀態,有26個自環轉移。
原問題可以看成從起始狀態$[1, |s|]$,通過$n$次轉移到終止狀態的方案數。
然而這有什麽用呢?
現在將計數的過程分成兩個步驟:
step 1
現在不考慮自環轉移。對於每走一條紅色狀態的向外轉移邊意味著未匹配的序列長度減一(轉移到$[l + 1, r]$或$[l, r - 1]$)。每走一條綠色狀態的向外轉移邊意味著序列長度減少2,除了形如$[i, i]$的狀態。但是由於它的例外很特殊,所以當我們確定路經紅色狀態的向外轉移邊的個數$n24$,那麽就有唯一確定的路經綠色狀態的向外轉移邊的個數$n25$與之對應,它的個數為$n25 = \left \lceil \frac{|s| - n24}{2} \right \rceil$。
用$f[l][r][k]$表示當前狀態為$[l, r]$,已經走過的紅色狀態的向外轉移邊的個數為$k$的方案數。現在約定用$[0, 0]$表示終止狀態。
這個動態規劃可以在$O(|s|^{3})$內完成,這就是第一部分的計數。
step 2
現在考慮將自環轉移插入路徑中。
考慮在路徑中插入自環轉移,使得路徑長度達到要求。
顯然,插入自環轉移的方案數之和紅色狀態、綠色狀態以及終止狀態的個數有關而與它們的排列順序無關。
因為對於兩個滿足上面三個狀態數相同但不同的簡單路徑,顯然無論我們怎麽插入自環最終都對應不同的回文串。另外我們可以對它們的節點建立一一映射。這樣就能證明上面這條優美的性質。然而這樣有什麽用呢?
考慮將紅色狀態扔到左邊,綠色狀態扔到右邊,每個綠色狀態接一個終止狀態(註意,每一個都需要一個不同的終止狀態)
如果紅色狀態足夠多,綠色狀態也足夠多,那麽對於每一條無標號有狀態類型的路徑都可以對應到這樣的一個自動機上從某個節點開始,到達終止狀態的路徑。
由於紅色狀態至多$|s| - 1$個(因為最後一個狀態一定有25個自環轉移),綠色狀態至多$\left \lceil \frac{|s|}{2} \right \rceil$個,終止狀態與綠色狀態數量相同。因此總狀態數是$O(|s|)$的,因此,我們可以在$O(|s|^{3}\log n)$的時間內解決這個問題。
結束了嗎?
當然沒有!讓我們來隨手打一組數據:
aa 1
期望26,讀到51。
為什麽會這樣?讓我們來回顧一下,在考慮特殊的綠色狀態的轉移時,我們讓它"自適應"是加入1個字符還是2個。但是在狀態$[l, l + 1]$,且滿足$s_{l} = s_{r}$時,如果轉移到終止狀態,那麽強制使得串長增加了2。如果$|s| + n$是奇數,且最後一個走過的節點是長度為2的綠色狀態,那麽這樣會使得最終的串長為偶數。現在考慮將它從答案中減去。如何避免這樣的情況?讓最後一個走過的節點沒有這樣的強制限制就好了。但是除了長度為2的綠色狀態這樣的限制,其他狀態都有"自適應"的能力,因此我們只需要減去在$|s| + \left \lceil \frac{n}{2} \right \rceil - 1$步在最後一個長度為2的綠色狀態的方案數就好了。
因為我們枚舉$n24$,所以另外$n25 = \left \lceil \frac{|s| - n24}{2} \right \rceil$,所以,可以根據$|s| - n24$的奇偶性來判斷最後一個綠色狀態的長度。
這樣就解決這個問題。然後還有一點想瞎扯一下。這算法的本質是先進行帶標號的計數,然後再進行無標號的計數,最後再進行標號。
當然還有一些神奇的解法,可以跑得賊快,只是我不會。打上Lazy Tag,以後再來學吧。
另外,如果代碼比較醜,請卡常數。一個不錯的卡常數的方法利用轉移矩陣是優美的上三角。矩陣乘法的時候可以將常數減小到原來的六分之一。
Code
1 /** 2 * Codeforces 3 * Problem#506E 4 * Accepted 5 * Time: 2355ms 6 * Memory: 44928k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 205, M = 10007; 13 14 typedef class Matrix { 15 public: 16 int a[N << 1][N << 1]; 17 18 Matrix() { } 19 Matrix(int n) { 20 for (int i = 0; i < n; i++) 21 for (int j = 0; j < n; j++) 22 a[i][j] = ((i == j) ? (1) : (0)); 23 } 24 25 int* operator [] (int p) { 26 return a[p]; 27 } 28 29 void debug() { 30 for (int i = 0; i < 11; i++, cerr << endl) 31 for (int j = 0; j < 11; j++) 32 cerr << a[i][j] << " "; 33 } 34 }Matrix; 35 36 char str[N]; 37 int n, m, es, l; 38 int f[N][N][N]; 39 40 inline void init() { 41 scanf("%s%d", str + 1, &m); 42 n = strlen(str + 1); 43 } 44 45 void modplus(int& a, int b) { 46 a += b; 47 if (a < 0) a += M; 48 if (a >= M) a -= M; 49 } 50 51 inline void dp() { 52 f[1][n][0] = 1; 53 for (int l = n - 1; ~l; l--) { 54 for (int i = 1, j; (j = i + l) <= n; i++) { 55 for (int k = 0; k <= n; k++) { 56 if (!f[i][j][k]) continue; 57 if (str[i] == str[j]) { 58 if (i + 1 == j || i == j) 59 modplus(f[0][0][k], f[i][j][k]); 60 else 61 modplus(f[i + 1][j - 1][k], f[i][j][k]); 62 } else { 63 modplus(f[i + 1][j][k + 1], f[i][j][k]); 64 modplus(f[i][j - 1][k + 1], f[i][j][k]); 65 } 66 } 67 } 68 } 69 /* 70 for (int i = 0; i <= n; i++) 71 cerr << "0 0 " << i << " " << f[0][0][i] << endl; 72 for (int i = 1; i <= n; i++) 73 for (int j = i; j <= n; j++) 74 for (int k = 0; k <= n; k++) 75 cerr << i << " " << j << " " << k << " " << f[i][j][k] << endl; 76 */ 77 } 78 79 Matrix operator * (Matrix a, Matrix b) { 80 Matrix rt; 81 for (int i = 0; i < es; i++) 82 for (int j = i; j < es; j++) { 83 rt[i][j] = 0; 84 for (int k = i; k <= j; k++) 85 rt[i][j] = (rt[i][j] + a[i][k] * b[k][j]) % M; 86 } 87 return rt; 88 } 89 90 Matrix qpow(Matrix& a, int pos) { 91 Matrix pa = a, rt = Matrix(es); 92 for ( ; pos; pos >>= 1, pa = pa * pa) 93 if (pos & 1) 94 rt = rt * pa; 95 return rt; 96 } 97 98 Matrix g, tem, bas; 99 inline void solve() { 100 int N24 = n - 1, N25 = (n + 1) >> 1, N26 = N25; 101 es = N24 + N25 + N26, l = (m + n + 1) >> 1; 102 for (int i = 0; i < N24; i++) 103 g[i][i + 1] = 1, g[i][i] = 24; 104 for (int i = N24; i < N24 + N25; i++) { 105 g[i][i + N25] = 1, g[i][i] = 25; 106 if (i < N24 + N25 - 1) 107 g[i][i + 1] = 1; 108 } 109 for (int i = N24 + N25; i < es; i++) 110 g[i][i] = 26; 111 112 bas = g; 113 114 tem = g = qpow(bas, l - 1); 115 // g.debug(); 116 // cerr << "hahaha" << endl; 117 118 g = g * bas; 119 120 // g.debug(); 121 122 int res = 0; 123 boolean aflag; 124 for (int i = 0, n25, c; i <= N24; i++) { 125 n25 = (n - i + 1) >> 1, aflag = !((n - i) & 1); 126 c = f[0][0][i]; 127 res = (res + c * g[N24 - i][N24 + N25 + n25 - 1]) % M; 128 if (((n + m) & 1) && aflag) 129 res = (res - (c * tem[N24 - i][N24 + n25 - 1]) % M + M) % M; 130 } 131 132 printf("%d\n", res); 133 } 134 135 int main() { 136 init(); 137 dp(); 138 solve(); 139 return 0; 140 }
Codeforces 506E Mr. Kitayuta's Gift - 動態規劃 - 矩陣