题解 P3193 【[HNOI2008]GT考试】

Edgration

2018-04-12 23:00:48

Solution

题解有点长请耐心观看... ## 题意 求有多少个$n$位的数字串不包含$m$位的字符串 (范围 $n <= 1e9 $, $m <= 20$) ## 分析 网上的题解我都看不懂啊QAQ(太弱了),那么我就写详细一点吧... ### 0x01 暴力 第一眼看到就是不会... 不会怎么办..先把暴力打了... 暴力枚举字符串,然后$Hash$比较 复杂度$O(10^n\cdot n)$,实测只能过$n<=7$的点 然后得到$10$分 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa913k95mj30no0gjgmr.jpg) ### 0x02 DP 妈妈我会$DP$ ! 根据套路,因为$m$并不会很大,设$f[i][j]$表示长串匹配到第$i$位,短串最多可以匹配到第$j$位的方案数 ![](https://ws4.sinaimg.cn/large/006tKfTcly1fqa7y12r2vj30ft079gm0.jpg) 那么为了让它不能找到完全的匹配,答案就是 $$\sum_{i=0}^{m-1}f[n][i]$$ 那么怎么转移? 考虑对于已经匹配了的$f[i][j]$转移到$f[i + 1][k]$新加一个新的字符造成的影响。 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa82b67ssj30ev05wq3f.jpg) 由于这个$new$是可以随便填写的,对于短串的$j+1$,可能会有几种情况。 1. new和$t+1$匹配,转移到$f[i+1][j+1]$ 2. 不匹配 ![](https://ws4.sinaimg.cn/large/006tKfTcly1fqa873h653j30f7081wfj.jpg) 如图,这样可能会找到一个新的长度为$k$的前缀使得和当前枚举到的后缀匹配 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa89i1k3aj30fv08gjs9.jpg) 甚至$k=0$无法匹配 其实,这个东西和枚举的字母无关,考虑到最后的答案,在$DP$过程中只需要考虑长度对答案造成的影响。 对于这一点,我们就是要知道,对于一个匹配到长度为$j$的串,转移到$k$的串的方案,也就对于长度为$i$的串,加一个数子,能加入多少种数字,使得长度为$j$的匹配变成长度为$k$的匹配(如图)。 $DP$式就可以写出来啦 $$f[i][j]=\sum_{k=0}^{m-1} f[i-1][k]*g[k][j]$$ $DP$套$DP$? 其实没那么麻烦,对于第二个的方案数,因为我们知道短串,这个“$g[i][j]$”是固定的,就可以预处理出结果。 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa8lwb5xyj309307p74l.jpg) 为了计算长度为$j$的已经匹配好了的串可以用多少种数字变为$k$,枚举一个数字,看它在短串中最长可以匹配到最多多长的前缀。 妈妈我会$Kmp$! 然后为了保证是最长的而且前面的东西保留,暴力枚举的复杂度好像有点爆炸,这个时候一看不就是一个裸$Kmp$吗,对于新的数字,失去匹配就跳$next$。 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa8rri4eej309p09bdgp.jpg) 于是就得到了一个$O(n*m^2)$的优秀算法.... 大概可以做$n<=250000$的数据,然后得到$40$分 ![](https://ws2.sinaimg.cn/large/006tKfTcly1fqa91ilorsj30np0f6my9.jpg) ### 0x03 矩阵快速幂 然后看一看$DP$式 $$f[i][j]=\sum_{k=0}^{m-1} f[i-1][k]*g[k][j]$$ 这不就是一个矩阵乘法的式子吗... 因为$g[i][j]$是固定的,于是把$f[i][j]$看成一个矩阵,对于矩阵$F[i]$它的第一层就是$f[i][j]$。 $$F[i]=F[i-1]*G$$ $G$是固定的,你又知道$F[0]$的第一行第一列是$1$,然后求一个$F[n]$就行了... 矩阵快速幂即可.... ### 0x04 代码 暴力 ``` #include<bits/stdc++.h> using namespace std; inline int read(){ char ch = getchar(); int u = 0, f = 1; while (!isdigit(ch)) { u = (u << 1) + (u << 3) + ch - 48; ch = getchar(); } while (isdigit(ch)) { u = (u << 1) + (u << 3) + ch - 48; ch = getchar(); } return u * f; } const int maxn = 1e5 + 10; int n, m, k; int ans = 0; typedef unsigned long long ull; ull ha_m; ull mod = 19260817; ull base = 266; ull ha[maxn], pw[maxn]; int s[maxn]; char md[maxn]; inline void build(){ ha[0] = 0; for (int i = 1; i <= n; ++i) ha[i] = (ha[i - 1] * base + s[i]) % mod; } inline ull split(int l, int r){ return ((ha[r] - (ha[l - 1] * pw[r - l + 1]) % mod) + mod) % mod; } inline void dfs(int x){ if (x == n + 1){ build(); for (int i = 1; i + m - 1 <= n; ++i){ if (split(i, i + m - 1) == ha_m) return; } ans++; return; } for (int i = 0; i <= 9; ++i){ s[x] = i; dfs(x + 1); } } int main(){ // freopen("data.txt", "r", stdin); n = read(), m = read(), k = read(); if (n >= 10) return 0 * puts("NO"); scanf("%s", md + 1); pw[1] = base; for (int i = 2; i <= n; ++i) pw[i] = (pw[i - 1] * base) % mod; for (int i = 1; i <= m; ++i) ha_m = (ha_m * base + (md[i] - '0')) % mod; dfs(1); printf("%d", ans % k); return 0; } ``` 40分DP ``` #include<bits/stdc++.h> using namespace std; inline int read(){ char ch = getchar(); int u = 0, f = 1; while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { u = (u << 1) + (u << 3) + ch - 48; ch = getchar(); } return u * f; } const int maxn = 1e6+10; int f[maxn][30], n, m, k; int nxt[50], match[50][50]; char md[50]; inline void Inittt(){ n = read(), m = read(), k = read(); scanf("%s", md + 1); } inline void Kmp(){ nxt[0] = -1; for (int i = 1; i <= m; ++i){ int j = nxt[i - 1]; while (j != -1 && md[j + 1] != md[i]) j = nxt[j]; nxt[i] = j + 1; } nxt[0] = 0; for (int i = 0; i < m; ++i){ for (int j = '0'; j <= '9'; ++j){ int temp = i; while (md[temp + 1] != j && temp > 0) temp = nxt[temp]; if (md[temp + 1] == j) temp++; if (temp < m) match[i][temp]++; } } } int main(){ Inittt(); Kmp(); f[0][0] = 1; for (int i = 1; i <= n; ++i) for (int j = 0; j < m; ++j) for (int p = 0; p < m; ++p) { (f[i][j] += f[i - 1][p] * match[p][j]) %= k; } int ans = 0; for (int i = 0; i < m; ++i) (ans += f[n][i]) %= k; printf("%d", ans); return 0; } ``` 100分代码(人傻大常数大 ``` #include<bits/stdc++.h> using namespace std; inline int read(){ char ch = getchar(); int u = 0, f = 1; while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { u = (u << 1) + (u << 3) + ch - 48; ch = getchar(); } return u * f; } const int maxn = 5050; int f[maxn][30], n, m, k; int nxt[maxn], match[maxn][50]; char md[maxn]; inline void Kmp(){ nxt[0] = -1; for (int i = 1; i <= m; ++i){ int j = nxt[i - 1]; while (j != -1 && md[j + 1] != md[i]) j = nxt[j]; nxt[i] = j + 1; } nxt[0] = 0; for (int i = 0; i < m; ++i){ for (int j = '0'; j <= '9'; ++j){ int temp = i; while (md[temp + 1] != j && temp > 0) temp = nxt[temp]; if (md[temp + 1] == j) temp++; if (temp < m) match[i][temp]++; } } } class MARTIX{ public: int mr[23][23]; MARTIX(){ memset(mr, 0, sizeof(m)); } void pr(){ for (int i = 0; i <= m; i++, cerr << endl) for (int j = 0; j <= m; ++j) cerr << mr[i][j] << " "; } MARTIX operator * (MARTIX B){ MARTIX Rtn; memset(Rtn.mr, 0, sizeof(Rtn.mr)); for (int i = 0; i < m; ++i) for (int j = 0; j < m; ++j) for (int p = 0; p < m; ++p) { (Rtn.mr[i][j] += mr[i][p] * B.mr[p][j]) %= k; } return Rtn; } }F, G; inline MARTIX ksm(MARTIX A, int pw){ MARTIX Rtn; for (int i = 0; i <= m; ++i) Rtn.mr[i][i] = 1; for (; pw; pw >>= 1, A = A * A) if (pw & 1) Rtn = Rtn * A; return Rtn; } signed main(){ n = read(), m = read(), k = read(); scanf("%s", md + 1); Kmp(); F.mr[0][0] = 1; for (int i = 0; i <= m; ++i) for (int j = 0; j <= m; ++j) G.mr[i][j] = match[i][j]; G = ksm(G, n); F = F * G; int ans = 0; for (int i = 0; i < m; ++i) { (ans += F.mr[0][i]) %= k; } printf("%d", ans); return 0; } ```