链接: [HNOI2008]GT考试
题意:
给出一个长度小于20的数字串 s,问有多少种长度为 n(n <= 1e9) 的数字串中不包含 s 。
思路:
- 首先不考虑数据范围,可以用dp[ i ][ j ]表示枚举到第 i 位当前后缀匹配 s 的最大前缀。最后dp[n][0 , m - 1]就是答案。
- 具体怎么转移呢,如果新加的这一位可以与 s 的下一位匹配,那么就可以转移到 dp[i + 1][j + 1]。如果不匹配就往回找第一个能匹配的位置,这里可以利用kmp的next数组往回跳,匹配一个新的前缀。假设找到的位置位 nex[j],那么当前状态就可以转移到dp[i + 1][nex[ j ] + 1]。
- 但是这题的数据范围有1e9,我们可以把 dp方程 变成
dp[i][j] = (dp[i][j] + dp[i - 1][k] * g[k][j])
,g[ k ][ j ],表示第 i 位有多少种选数的方案使得匹配由 k 位跳回 j 位。然后求出 g 矩阵 , 用矩阵快速幂优化这个递推过程就好了。可以发现 dp[i]×g后会变成dp[i + 1]。
代码:
暴力dp代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <math.h>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 200 + 7;
int n , m , mod, nex[maxn];
int dp[100000][maxn];
int g[maxn][maxn];
char s[maxn];
void getnext(){
nex[0] = -1;
int i = 0,j = -1;
while(i <= m){
if(j == -1 || s[i] == s[j]){
i ++;
j ++;
nex[i] = j;
}
else j = nex[j];
}
}
int check(int k , int j ,int now){
//已经匹配 k 位 加上新的一位 now 后 能否匹配 j 位
k = nex[k]; //如 132134 13213 已经匹配 5 位 新加一个2 变成 123132 可以匹配 3 位
while(k != - 1){
if(now == s[k] - '0'){
if(k == j - 1) return 1;
return 0;
}
k = nex[k];
}