题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2825
解题思路:
给出n个串,询问至少包含其中k个串的长度为L的串的种类
AC自动机的作用就是为了枚举当前节点的所有自节点时找到最优的位置使得尽快到达单词结尾,以及到达某一节点时获得自己以及所有后缀链接的所有到达的单词末尾的所有情况(构造时直接处理)
状压DP:
用k表示状态。
eg. k=1023,在二进制下=1111111111,表示有1~10这10种不同的串
定义:dp[i][j][k],从根节点出发,长度为i,第i个字符停在j节点上,且拥有k对应的串种类的方法数
初始状态:dp[0][0][0] = 1
状态转移:dp[i][j][k] = (所有dp[i-1][ ][ ]中可以转移到当前状态的),但是枚举当前状态所有先前状态不好写,所以我们枚举所有当前状况累加到当前状态所有能到达的下一步状态即可。
u表示当前节点,用v表示当前节点某个能到的下一节点,val[v]表示v节点的“k值”
dp[i+1][v][ k|val[v] ] += dp[i][u][k];
一个需要想清楚的东西:当前这个状态能不能被枚举?
能被枚举意味着一定能到达这个点,(否则用它来给他的子节点计数就是错误的因为目前长度到不了)即到达这个点方法数至少为1,即dp[i][u][k]>0。
(否则应该需要根据当前的深度,枚举字典树所有深度<=当前深度的点,那样很麻烦)
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define rof1(i,a,b) for (int i=a;i>=b;i--)
#define rof0(i,a,b) for (int i=a;i>b;i--)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
#define dfl(x) ll x;scanf("%I64d",&x)
#define df2l(x,y) ll x,y;scanf("%I64d %I64d",&x,&y)
#define df(x) int x;scanf("%d",&x);
#define df2(x,y) int x,y;scanf("%d %d",&x,&y)
#define mod 20090717
#define duozu(T) int T;scanf("%d",&T);while (T--)
const int N = 10+5;
const int maxnode = 110+5;//模式串数量*长度
const int ALP = 26;//字符种类数
char s[N];
int dp[26][105][1023+5];
int cnt1[1023+5];
struct AC_am
{
queue<int>que;
int sz;
int trie[maxnode][ALP];
int fail[maxnode];
int last[maxnode];
int val[maxnode];
int newnode(int x){
memset(trie[x],0,sizeof trie[x]);
val[x] = 0;
return sz++;
}
void init(){
newnode(sz = 0);
}
int idx(char ch){//实际字符串转化为字典树对应节点,根据题目做出具体改变
return ch-'a';
}
void insert(char *s,int bj){
int u = 0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
if (!trie[u][c]){
trie[u][c] = newnode(sz);
}
u = trie[u][c];
}
val[u] = bj;///如果有多个串重复,这里写val|=bj
}
void build(){
fail[0] = 0;
for (int c=0;c<ALP;c++){
int v = trie[0][c];
if (v){
que.push(v);
fail[v] = 0;
last[v] = 0;
}
}
while (!que.empty()){
int u = que.front();que.pop();
for (int c=0;c<ALP;c++){
int v = trie[u][c];
if (!v){
trie[u][c] = trie[fail[u]][c];
continue;
}
fail[v] = trie[fail[u]][c];
last[v] = val[fail[v]]? fail[v]:last[fail[v]];
val[v] |= val[last[v]];
que.push(v);
}
}
}
}ac;
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/output.txt", "w", stdout);
int L,n,kk;
///预处理每个数1的数量
for (int i=0,j;i<=1023;i++){
int cnt = 0;
j=i;
while (j){
if (j&1) cnt++;
j>>=1;
}
cnt1[i] = cnt;
}
while (~scanf("%d %d %d",&L,&n,&kk) && (L|n|kk)){
ac.init();
memset(dp,0,sizeof dp);
for0(i,0,n) scanf("%s",s),ac.insert(s,1<<i);
ac.build();
dp[0][0][0] = 1;
for (int i=0;i<L;i++)
for (int u=0;u<ac.sz;u++)
for (int k=0;k<(1<<n);k++){///0~2^n-1
if (!dp[i][u][k]) continue;
for (int c=0;c<26;c++){
int v = ac.trie[u][c];
dp[i+1][v][k|ac.val[v]] += dp[i][u][k];
dp[i+1][v][k|ac.val[v]] %= mod;
}
}
///找出所有长度为L,至少有k位1的状态,求和
ll ans = 0;
for (int u=0;u<ac.sz;u++){
for (int k=0;k<(1<<n);k++)
if (cnt1[k]>=kk) ans += dp[L][u][k];
ans %=mod;
}
printf("%lld\n",ans);
}
return 0;
}
总结:做AC自动机的题目做自闭了,我根本不会DP和矩阵。。。awsl