HDU2825
题目描述
给你 m m m个模式串,问你构建长度为 n n n至少包含 k k k个模式串的方案有多少种?
题解
首先对模式串建立AC自动机
定义
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
当
前
长
度
为
i
]
[
当
前
在
A
C
自
动
机
第
j
个
节
点
]
[
当
前
选
择
了
k
集
合
的
模
式
串
(
状
态
压
缩
)
]
dp[i][j][k]=dp[当前长度为i][当前在AC自动机第j个节点][当前选择了k集合的模式串(状态压缩)]
dp[i][j][k]=dp[当前长度为i][当前在AC自动机第j个节点][当前选择了k集合的模式串(状态压缩)]=方案数
然后正常转移即可
代码
#include<bits/stdc++.h>
using namespace std;
int dp[28][110][1<<10],tr[110][26],val[110],cnt,n,m,kk,ans,fail[110];
char s[15];
const int mod=20090717;
void clear(){//多组数据注意清空
cnt=ans=0;
memset(tr,0,sizeof(tr));
memset(val,0,sizeof(val));
memset(dp,0,sizeof(dp));
memset(fail,0,sizeof(fail));
}
int get1(int x){
int sum=0;
while(x){
if(x&1) sum++;
x>>=1;
}return sum;
}
void build(int num){
int len=strlen(s+1),u=0;
for(int i=1;i<=len;i++){
int c=s[i]-'a';
if(!tr[u][c]) tr[u][c]=++cnt;
u=tr[u][c];
}val[u]=(val[u]|(1<<(num-1)));//val值的赋值
}
void getfail(){
queue<int>q;
for(int i=0;i<26;i++) if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=0;i<26;i++){
if(!tr[u][i]) tr[u][i]=tr[fail[u]][i];
else{
int x=tr[u][i],y=fail[u];q.push(x);
while(y&&!tr[y][i]) y=fail[y];
fail[x]=tr[y][i];val[x]|=val[fail[x]];//注意val值更新
}
}
}
}
void DP(){
dp[0][0][0]=1;//注意初始化
for(int i=0;i<n;i++)
for(int j=0;j<=cnt;j++)//这里cnt要取
for(int k=0;k<(1<<m);k++){
if(dp[i][j][k]){
for(int l=0;l<26;l++){
int x=tr[j][l],y=(val[x]|k);
dp[i+1][x][y]=(dp[i+1][x][y]+dp[i][j][k])%mod;
}
}
}
for(int i=0;i<=cnt;i++)
for(int k=0;k<(1<<m);k++){
if(get1(k)>=kk) ans=(ans+dp[n][i][k])%mod;
//printf("%lld\n",dp[n][i][k]);
}
printf("%d\n",ans%mod);
}
signed main(){
while(scanf("%d%d%d",&n,&m,&kk)){
if(n==0&&m==0&&kk==0) break;
clear();
for(int i=1;i<=m;i++){
scanf("%s",s+1);
build(i);
}getfail(),DP();
}return 0;
}
总结
1,对于是包含模式串的限制,我们通常用状压
d
p
dp
dp表示选了哪些串,且AC自动机的节点需要保留选串的情况(即
v
a
l
[
]
val[]
val[])
2,注意循环的内外顺序,一般情况下,字符串长度的循环都是放在外层,因为只有先计算出长度为
i
i
i的所有字符串状态,才能计算长度为
i
+
1
i+1
i+1的所有字符串状态。