3802: Vocabulary
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 193 Solved: 70
[Submit][Status][Discuss]
Description
给你三个字符串,这些字符串有些单词模糊不可认了,用"?"来代表。
现在你可以用任意英文小写字母来代表它们。要求是使得给定的三个字符串中
所有的"?"被你认定的字母代替后,各不相同且按字典序出现。问有多少种方式。
Input
先给出一个数字N,代表数据组数。
接下来3*N行,每行给出一个字符串。长度<=1000 000
Output
输出结果 Mod 10^9+9
Sample Input
3
?heoret?cal
c?mputer
?cience
jagiellonia
?niversity
kra?ow
?
b
c
?heoret?cal
c?mputer
?cience
jagiellonia
?niversity
kra?ow
?
b
c
Sample Output
42562
52
1
52
1
分析:一道特别好的题目!
这种计数题一看就知道要用dp做,但是状态怎么定义呢?首先肯定要有一维i表示到了第i个字符. 仅仅只有这一维不能转移,字符串的长度有100w,那么另一维肯定特别小,猜测一下,应该是表示的某种状态.
如果将3个字符串的状态用数字表示. 0表示s1 < s2 < s3,1表示s1 < s2 = s3,2表示s1 = s2 < s3,3表示s1 = s2 = s3.最后要求的状态就是0.这4种状态是可以互相转化的,而且关键因素在于最后一个字符.那么可以令f[i][j]表示3个字符串的前i个字符中,状态为j的方案数,每次转移就枚举3个字符串i位置上的字符,转移到i+1.总复杂度O(n*26^3).
这样显然会超时.考虑如何优化它. 算法的瓶颈在于每次转移都必须要枚举3个位置上的字符. 前面说过,4种状态的转化关键因素在于最后一个字符.那么我们可以预处理出一个数组g[i][j][k][s][t]表示3个字符串分别往后加字符i,j,k,从状态s转移到状态t的方案数.这个预处理就很简单了,枚举i,j,k,s,t,分类讨论一下就可以了.
最后的转移方程式就是f[i][k] = f[i - 1][j] * g[x][y][z][j][k]. x,y,z分别对应3个字符串第i位的字符.
重要的思想,预处理. 如果dp转移出现大量的重复枚举并且这些枚举得到的值都是可以预先算出来的话,就先算出来存到数组里.
我认为这道题和bzoj1009比较像,它们完全不是一类题,为什么我会这么想呢?bzoj1009的解法中,我知道f(i)可以转移到f(i+1)的哪些状态去,但并不能很容易地知道f(i)由f(i-1)的哪些状态转移来,于是就开了一个数组,存储f(i - 1)的某个状态到f(i)的某个状态的方案数.枚举各种情况乘一下就好了.
这道题也可以这么理解.也是比较难知道f[i][j]由f[i-1]的哪些状态推来,没关系,维护一个数组保存方案数就好了嘛.
坑点:直接memset f数组会T掉,需要手动清空,也就是数组很大,但是串长远远不到.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int mod = 1e9+9,maxn = 1000010; int T,len1,len2,len3,len; char s1[maxn],s2[maxn],s3[maxn]; int a1[maxn],a2[maxn],a3[maxn]; ll f[maxn][5],g[30][30][30][5][5]; void init() { for (int i = 0; i <= 27; i++) for (int j = 0; j <= 27; j++) for (int k = 0; k <= 27; k++) { int l1,l2,l3,r1,r2,r3; if (i == 27) l1 = 1,r1 = 26; else l1 = r1 = i; if (j == 27) l2 = 1,r2 = 26; else l2 = r2 = j; if (k == 27) l3 = 1,r3 = 26; else l3 = r3 = k; for (int x = l1; x <= r1; x++) for (int y = l2; y <= r2; y++) for (int z = l3; z <= r3; z++) for (int s = 0; s <= 3; s++) { int t = -1; if (s == 0) t = 0; else if (s == 1) { if (y == z) t = 1; else if (y < z) t = 0; else t = -1; } else if (s == 2) { if (x == y) t = 2; else if (x < y) t = 0; else t = -1; } else { if (x == y && y == z) t = 3; else if (x == y && y < z) t = 2; else if(x < y && y == z) t = 1; else if (x < y && y < z) t = 0; else if (x > y || y > z || x > z) t = -1; } if (t != -1) g[i][j][k][s][t]++; } } } int main() { init(); scanf("%d",&T); while (T--) { scanf("%s",s1 + 1); len1 = strlen(s1 + 1); scanf("%s",s2 + 1); len2 = strlen(s2 + 1); scanf("%s",s3 + 1); len3 = strlen(s3 + 1); for (int i = 1; i <= len1; i++) a1[i] = s1[i] - 'a' + 1; for (int i = 1; i <= len2; i++) a2[i] = s2[i] - 'a' + 1; for (int i = 1; i <= len3; i++) a3[i] = s3[i] - 'a' + 1; for (int i = len1 + 1; i <= len; i++) a1[i] = 0; for (int i = len2 + 1; i <= len; i++) a2[i] = 0; for (int i = len3 + 1; i <= len; i++) a3[i] = 0; len = max(len1,max(len2,len3)); for (int i = 1; i <= len; i++) for (int j = 0; j <= 3; j++) f[i][j] = 0; f[0][3] = 1; for (int i = 1; i <= len; i++) { int x = a1[i],y = a2[i],z = a3[i]; if (x < 0) x = 27; if (y < 0) y = 27; if (z < 0) z = 27; for (int j = 0; j <= 3; j++) { if (f[i - 1][j]) { for (int k = 0; k <= 3; k++) { f[i][k] = (f[i][k] + f[i - 1][j] * g[x][y][z][j][k] % mod) % mod; } } } } printf("%lld ",f[len][0] % mod); } return 0; }