标签:ref 位置 i++ details 防止 遇到 大小写 ack lin
给N个串,然后再给一个串s,求N个串总共在S中出现了多少次
将N个串插入到AC自动机当中,如果某个结点为模式串末尾结点,则在fail树中,以该节点为祖先的所有结点,贡献都+1
然后直接暴力匹配即可,最后倒着去算一遍贡献。(具体体现在fail树中,如果一个结点匹配上了,那么他的祖先一定都可以匹配上,按序号降序去累加可以保证顺序是正确的,因为一个结点的fail指针所指结点的序号一定更小)
需要注意的是,N个串中可能有相同的。
const int N = 1000010 + 5;
int n;
char s[N], P[200][100];
map<string,int> mp;
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
int cnt[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = cnt[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
mp[string(s+1)] = u;
e[u] ++;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void get(char *t){
int u = 0,res = 0;
for(int i=1;t[i];i++){
u = tr[u][t[i] - 'a'];
cnt[u] ++;
}
for(int i=tot;i>=0;i--) cnt[fail[i]] += cnt[i];
for(int i=0;i<=tot;i++) if(e[i]){
res = max(res, cnt[i]);
}
printf("%d\n", res);
for(int i=1;i<=n;i++){
if(cnt[mp[string(P[i]+1)]] == res) printf("%s\n", P[i]+1);
}
}
}
int main() {
while(~scanf("%d",&n)){
if(n == 0) break;
mp.clear();
AC::init();
for(int i=1;i<=n;i++){
scanf("%s", P[i]+1);
AC::insert(P[i]);
}
AC::build();
scanf("%s",s+1);
AC::get(s);
}
return 0;
}
给 K 个串,再给一个大小为N的字符集,给出从N个字符中选第 i 个的概率 p_i, 然后每次从中选出一个字符,选L次拼成一个长为L的字符串,求K个串不出现在该串中的概率。
AC自动机中的trans函数,即状态转移函数,如果 \(trans[u][c]\) 所指向的结点是一个字符串的末尾结点或者是末尾结点在fail树上的子孙,那么该转移不能选择,如果选择则意味着有一个字符串会出现在构造的这个串中。
到这里就变成了一个概率DP的问题,为了降低编程难度,可以直接用记忆化搜索实现。
还需要注意的是字符集的问题,它包括了大小写以及数字。
const int N = 1010;
int n, k, L, vis[1010][200], in[300];
double p[300], d[1010][200];
char s[22][22];
namespace AC{
int tr[N][300],tot;
int e[N],fail[N];
int cnt[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = cnt[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i]]){
tr[u][s[i]] = ++tot;
}
u = tr[u][s[i]];
}
e[u] ++;
}
void build(){
for(int i=0;i<130;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] |= e[fail[u]]; // e[u]=1则表示u结点所表示的状态的某一个后缀对应K个字符串中的某个
for(int i=0;i<130;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
double get(int u, int L){
if(!L) return 1.0;
if(vis[u][L]) return d[u][L];
vis[u][L] = 1;
double& ans = d[u][L];
ans = 0;
for(int i=0;i<130;i++){
//in[i] 则表示该字符出现在题目给定的字符集中
if(in[i] && !e[tr[u][i]]) ans += p[i] * get(tr[u][i], L-1);
}
return ans;
}
}
int main() {
int T;scanf("%d",&T);
int cas = 0;
while(T--){
memset(vis, 0, sizeof vis);
memset(in, 0, sizeof in);
AC::init();
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%s", s[i]+1);
AC::insert(s[i]);
}
scanf("%d",&n);
for(int i=1;i<=n;i++){
char op[10];scanf("%s", op);
scanf("%lf", &p[op[0]]);
in[op[0]] = 1;
}
AC::build();
scanf("%d",&L);
printf("Case #%d: %.6f\n", ++cas, AC::get(0, L));
}
return 0;
}
题意:给出一个\(n*m\)的字符矩阵\(T\),你的任务是找出给定的\(x*y\) 的字符矩阵\(P\) 出现了多少次。
分析:将 \(P\) 的每一行加入到AC自动机中,然后对于\(T\) 的每一行,去自动机中暴力匹配,如果找到了 \(T\) 的第 \(row\) 行的第 \(i\) 个位置匹配到了 \(P\) 第 \(j\)行字符的最后一个位置,说明在 \(T[row][i-y+1]\) 开始的位置可以匹配 P 的 \(j\) 行。这样,以 \(T[row-j+1][i-y+1]\) 为左上角的,大小为\(x*y\) 的子矩阵中,新增加了一行可以与 \(P\) 匹配,这个信息用\(match[i][j]\) 来存放,每次遇到使其加一。
最后跑一遍,满足\(match[i][j] = x\) 的那些\((i,j)\) 就是一个二位匹配点。
const int N = 100000 + 5;
int n, m, x, y;
char s[1010][1010], t[110][110];
int match[1010][1010];
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
int cnt[N];
vector<int> ends[N];
queue<int> q;
void init(){
memset(match, 0, sizeof match);
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = cnt[i] = 0;
ends[i].clear();
}
tot = 0;
}
void insert(char *s, int pos){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] ++;
ends[u].push_back(pos); // 将行序号加到对应结点末尾
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
for(int row=1;row<=n;row++){
int u = 0;
for(int i = 1; i <= m; i++){
u = tr[u][s[row][i] - 'a'];
//这里并不需要跳fail,因为这些模式串的长度都一定
for(auto pos : ends[u]){
match[row - pos + 1][i - y + 1] ++;
}
}
}
int res = 0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(match[i][j] == x) res++;
printf("%d\n", res);
}
}
int main() {
int T;scanf("%d",&T);
while(T--){
AC::init();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
}
scanf("%d%d",&x,&y);
for(int i=1;i<=x;i++){
scanf("%s",t[i]+1);
AC::insert(t[i], i);
}
AC::build();
AC::solve();
}
return 0;
}
比较恶心的多模式串匹配,需要预处理输入,将base64转换为uchar类型
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int id[200], a[100000];
char s[10000];
int t[10000];
int n;
void init(){
for(int i=0;i<64;i++){
id[cb64[i]] = i;
}
}
void pushback(int *a, int &pos, int x){
for(int i=0;i<6;i++){
a[pos++] = x >> (5-i) & 1;
}
}
int convert(char * s, int *t){ // s为base64,t为二进制
int len = strlen(s), pos = 0, top = 0;
for(int i=0;i<len;i++){
if(s[i] == '=')continue;
pushback(a,pos, id[s[i]]);
}
int x = 0;
for(int i=0;i<pos;i++){
x = x * 2 + a[i];
if((i+1) % 8 == 0){
t[top++] = x;
x = 0;
}
}
return top;
}
namespace AC{
int tr[N][256],tot;
int e[N],fail[N];
int st[N], top, v[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(int *s, int len){
int u = 0;
for(int i=0;i<len;i++){
if(!tr[u][s[i]]){
tr[u][s[i]] = ++tot;
}
u = tr[u][s[i]];
}
e[u] ++;
}
void build(){
for(int i=0;i<256;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
for(int i=0;i<256;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(int *s, int len){
int res = 0, u = 0;
for(int i=0;i<len;i++){
u = tr[u][s[i]];
for(int j=u; j && !v[j]; j=fail[j]){
res += e[j];
st[++top] = j;
v[j] = 1;
}
}
for(int i=1;i<=top;i++)v[st[i]] = 0;
top = 0;
printf("%d\n", res);
}
}
int main(){
init();
while(~scanf("%d",&n)){
AC::init();
for(int i=1;i<=n;i++){
scanf("%s",s);
int len = convert(s, t);
AC::insert(t, len);
}
AC::build();
scanf("%d",&n);
while(n--){
scanf("%s",s);
int len = convert(s, t);
AC::solve(t, len);
}
puts("");
}
return 0;
}
不难想到每个字符串结尾结点在fail树上的子孙代表的含义,对于一个询问\((x,y)\) ,要求的就是 x 号字符串的末尾结点的子树中,有多少个结点可以匹配到 y 中。
暴力的把所以标记打到fail树上?N个字符串,每个字符串长度不固定,有可能都很长,显然不行。每次对单个询问处理貌似都不太合适,可以把询问离线,集中处理询问。
先将fail树构建dfs序列,那么询问某个结点的子树情况可以转换为一个序列问题,接下来可以dfs扫描原Trie树(不是AC自动机构建出来的字典图),每次扫描到一个结点时,若从根节点到该结点的路径表示为询问中的 y,那么扫描它的所有询问 x,在fail树dfs序上找答案即可。
#define mk make_pair
const int N = 100000 + 5;
char s[N];
int n, m;
vector<pair<int,int>> query[N];
int c[N], res[N], pos[N];
void add(int x, int y){
for(;x<N;x+=x&-x) c[x]+=y;
}
int ask(int x){
int res = 0;
for(;x;x-=x&-x) res+=c[x];
return res;
}
namespace AC{
int tr[N][26],tot, trie[N][26];
int e[N], fail[N], fa[N];
int u;
int dfn[N], sz[N], cnt;
vector<int> ends[N];
vector<int> v[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
u = 0;
}
void insert(char c){
if(!tr[u][c-'a'])tr[u][c-'a'] = ++tot,fa[tot] = u;
u = tr[u][c-'a'];
}
void setend(int id){
e[u] ++;
ends[u].push_back(id);
pos[id] = u;
}
void getup(){
if(u != 0)
u = fa[u];
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] ++;
}
void build(){
for(int i=0;i<=tot;i++) memcpy(trie[i], tr[i], sizeof tr[i]);//保留原Trie树
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
if(u) v[fail[u]].push_back(u);//构建fail树
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void dfs(int u){//构建fail树dfs序列
dfn[u] = ++cnt;
sz[u] = 1;
for(auto x : v[u]){
dfs(x);
sz[u] += sz[x];
}
}
void solve(int u){
add(dfn[u], 1);
for(auto y : ends[u]){
for(auto t : query[y]){
int x = t.first;
int id = t.second;
int dfnid = dfn[pos[x]];
res[id] = ask(dfnid + sz[pos[x]] - 1) - ask(dfnid-1);
}
}
for(int i=0;i<26;i++){
if(trie[u][i])solve(trie[u][i]);
}
add(dfn[u], -1);
}
}
int main() {
scanf("%s",s);
AC::init();
int len = strlen(s);
n = 0;
for(int i=0;i<len;i++){
if(s[i] == 'B')AC::getup();
else if(s[i] == 'P'){
AC::setend(++n);
}else AC::insert(s[i]);
}
AC::build();
AC::dfs(0);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
query[y].push_back(mk(x, i));//离线询问
}
AC::solve(0);
for(int i=1;i<=m;i++) printf("%d\n", res[i]);
return 0;
}
AC自动机上面DP即可,注意要记忆化以下
const int N = 1000 + 5;
int n;
char s[N];
int xid[26];
namespace AC{
int tr[N][4],tot;
int e[N],fail[N];
int vis[N][N], d[N][N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(vis[i], 0, sizeof vis[i]);
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = xid[s[i] - 'A'];
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u] ++;
}
void build(){
for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] |= e[fail[u]];
for(int i=0;i<4;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
int get(int u, int pos, int len){
if(e[u]) return inf;//这个要放在最前面,u肯定是合法结点(不会超过tot),由于我第一次写好没放最前面,debug好久
if(pos == len + 1) return 0;
if(vis[u][pos]) return d[u][pos];
vis[u][pos] = 1;
int &res = d[u][pos];
res = inf;
int c = xid[s[pos] - 'A'];
for(int i=0;i<4;i++){
res = min(res, (i != c) + get(tr[u][i], pos+1, len));
}
return res;
}
}
int main() {
xid['A' - 'A'] = 0;
xid['G' - 'A'] = 1;
xid['C' - 'A'] = 2;
xid['T' - 'A'] = 3;
int cas = 0;
while(scanf("%d",&n) != EOF){
if(n == 0) break;
AC::init();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
AC::insert(s);
}
AC::build();
scanf("%s",s+1);
int res = AC::get(0, 1, strlen(s+1));
if(res == inf) res = -1;
printf("Case %d: %d\n",++cas, res);
}
return 0;
}
同样也是AC自动机上面DP,其实很好做的题,肯定是由于出题人懒得写spj导致一堆特殊条件
\(d[u][i]\) 表示从 \(u\) 结点出发,走 \(i\) 的长度,最多可以匹配多少价值,\(p[u][i]\) 表示路径。
先求出\(d[0][n]\), 然后找一个最小的 i , 使得出\(d[0][i] == d[0][n]\),然后按照路径输出。
由于枚举时是按照字典序从小到大的,所以这里直接输出即可
const int N = 2000 + 5;
const int M = 55;
char s[N];
int n, m, pos[N];
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
int vis[N][M], d[N][M], p[N][M];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(vis[i], 0, sizeof vis[i]);
memset(tr[i],0,sizeof tr[i]);
memset(p, 0, sizeof p);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s, int id){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = s[i] - 'a';
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
pos[id] = u;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] += e[fail[u]];//权值累加
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
int get(int u, int n){ // 从 u 开始 n 长度
if(n == 0) return 0;
if(vis[u][n]) return d[u][n]; // 记忆化搜索
vis[u][n] = 1;
int &ans = d[u][n];
ans = 0;
for(int i=0;i<26;i++){
int c = tr[u][i];
int now = e[c] + get(c, n - 1);
if(now > ans){ // 必须为大于号,因为答案要求字典序最小
ans = now;
p[u][n] = i;
}
}
return ans;
}
void print(int u, int n){
if(n == 0) return;
int c = p[u][n];
printf("%c", char('a'+c));
print(tr[u][c], n-1);
}
}
int main() {
int T;
scanf("%d",&T);
while(T--){
AC::init();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%s",s+1);
AC::insert(s, i);
}
for(int i=1;i<=m;i++){
scanf("%d", &AC::e[pos[i]]);
}
AC::build();
AC::get(0, n);
for(int i=0;i<=n;i++){
if(AC::get(0, i) == AC::get(0, n)){ //这里一定要用这种方式访问dp值!!!,由于是记忆化搜索所以再调用函数之前不一定会有d[0][i],被这里坑了
n = i;
break;
}
}
AC::print(0,n);
puts("");
}
return 0;
}
暴力跳\(fail\)进行统计。每次跳到一个结点$ j$,表示该结点表示的子串在 \(s\) 中出现了一次,对于第一类询问,直接++,对于第二类询问,需要利用上一次该位置在 \(s\) 中出现的位置\(last\),如果 \(i - last >= len(j)\),则表示两次出现在 \(s\) 中不交叉,则答案++.
因为本身模式串的长度最长才6,暴力跳fail几乎对时间没有影响。但如果还想优化,可以这样考虑,每次暴力跳fail的结果不一定会对计算结果有用,因为有一些结点并不是末尾结点,为了防止跳到这些没有用的结点,再用一个\(pre\) 数组来记录,在\(bfs\) 的过程中可以这么计算:
if(e[fail[u]]) pre[u] = fail[u];
else pre[u] = pre[fail[u]];
实际优化不是很明显:
const int N = 600000 + 5;
const int M = 100010;
int n;
char s[M], t[10];
int tp[M], res[N][2], last[N], dep[N], pos[M];
namespace AC{
int tr[N][26],tot;
int e[N],fail[N], pre[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
res[i][0] = res[i][1] = 0;
fail[i] = e[i] = 0;
last[i] = 0;
}
tot = 0;
}
void insert(char *s, int id){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] ++;
dep[u] = len;
pos[id] = u;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
if(e[fail[u]]) pre[u] = fail[u];
else pre[u] = pre[fail[u]];
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void get(){
int len = strlen(s+1);
int u = 0;
for(int i=1;i<=len;i++){
int c = s[i] - 'a';
u = tr[u][c];
for(int j = u; j; j = pre[j]){
if(e[j]){
res[j][0] ++;
if(i - last[j] >= dep[j]){
last[j] = i;
res[j][1] ++;
}
}
}
}
}
}
int main() {
int cas = 0;
while(~scanf("%s",s+1)){
AC::init();
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%s",&tp[i], t+1);
AC::insert(t, i);
}
AC::build();
AC::get();
printf("Case %d\n", ++cas);
for(int i=1;i<=n;i++){
printf("%d\n", res[pos[i]][tp[i]]);
}
puts("");
}
return 0;
}
http://codeforces.com/problemset/problem/963/D
给出一个字符串S, n次询问,每次询问给出正整数k和字符串m, 要求字符串T的最小长度,满足T是S的子串,且m在T中出现了至少K次。
首先想一下暴力做法:将所有询问串构建AC自动机,然后在上面跑S,若在\(s[i]\) 匹配到了一个串,那么就给这个串添加一个 i 位置为结尾的出现。最后进行统计即可(连续 k 次出现的最小长度)。
每次匹配到一个位置几乎都要暴力跳 fail,因为这个题目中多模式串的长度有长有短,很容易出现某个串是另外一个串的后缀这种情况,所以很容易T。但根据上一道题目中提到的小优化进行优化呢?每次只给对答案有用的结点添加出现位置,那最终添加的次数就是每个询问串在 s 中的所有出现位置。似乎还是会有很多。
注意到,题目中说明了每个询问串都不一样。对于长度一样的询问串,在S中最多有\(|S|\) 次出现,而对于总长度为\(\sum|m|\) 的多模式串来讲,\(|m|\) 最多有\(\sqrt{\sum|m|}\) 种,也就是将\(\sum|m|\) 最多分成\(\sqrt{\sum|m|}\)个不同的数字\((sum = \frac{n*(n+1)}{2})\) \(n\) 差不多在\(\sqrt{sum}\) 级别。
所以最后最多会添加\(\sqrt{\sum|m|}\times|S|\) 次位置。
const int N = 100000 + 5;
int n;
char s[N], t[N];
vector<int> pos[N];
int p[N], k[N], len[N];
namespace AC{
int tr[N][26],tot;
int e[N], fail[N], pre[N];
queue<int> q;
void insert(char *s, int id){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] ++;
p[u] = id;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
if(e[fail[u]]) pre[u] = fail[u];
else pre[u] = pre[fail[u]];
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void get(){
int len = strlen(s+1);
int u = 0;
for(int i=1;i<=len;i++){
int c = s[i] - 'a';
u = tr[u][c];
for(int j = u; j; j = pre[j]){
if(e[j]){
pos[p[j]].push_back(i);
}
}
}
}
}
int main() {
scanf("%s",s+1);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%s",&k[i], t+1);
len[i] = strlen(t+1);
AC::insert(t, i);
}
AC::build();
AC::get();
for(int i=1;i<=n;i++){
if(pos[i].size() < k[i]){
puts("-1");continue;
}
int res = inf;
for(int j = k[i] - 1; j < pos[i].size(); j++){
res = min(res, pos[i][j] - pos[i][j - k[i] + 1] + len[i]);
}
printf("%d\n", res);
}
return 0;
}
很疑惑为什么网上有那么多错误题解?
下面这份代码建立在资源串不存在某个是某个子串的情况,再建好的Trie图中,从每个资源串末尾进行BFS,求出它到其他资源串末尾结点的最短距离(不能经过病毒串末尾结点),当然还要处理空串到其他所有资源串的距离。
然后跑一个TSP就可以了
const int N = 100000 + 5;
int n, m;
char s[N];
namespace AC{
int tr[N][2],tot;
int e[N],fail[N], vir[N], pos[20];
int dis[20][20], d[N], dp[1<<10][20];//dp[i][j]表示资源串状态为 i(二进制),走到了 j 号资源串的末尾结点
queue<int> q;
void init(){
memset(dis, -1, sizeof dis);
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = vir[i] = 0;
}
tot = 0;
}
void insert(char *s, int id, int type){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - '0']){
tr[u][s[i] - '0'] = ++tot;
}
u = tr[u][s[i] - '0'];
}
if(type == -1){
vir[u] = 1;//标记病毒
}else{
pos[id] = u;
}
}
void build(){
for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
vir[u] |= vir[fail[u]];//fail树上进行传递
for(int i=0;i<2;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void bfs(int s){
queue<int> q;
memset(d, -1, sizeof d);//-1表示不能访问,这里要和dis的默认一致,都为-1
d[pos[s]] = 0; // 时刻注意pos[s]表示Trie图中s号资源串的结点编号
q.push(pos[s]);
while(q.size()){
int x = q.front();q.pop();
for(int i=0;i<2;i++){
int u = tr[x][i];
if(!vir[u] && d[u] == -1){
d[u] = d[x] + 1;
q.push(u);
}
}
}
for(int i=1;i<=n;i++){
dis[s][i] = d[pos[i]];
}
}
void solve(){
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
for(int i=0; i<(1<<n); i++){
for(int j=0; j<=n; j++){ //j要从0开始,
for(int k = 1; k <= n; k++){ //k从1开始就行,0代表空串
if(dis[j][k] == -1)continue;
if(i >> (k-1) & 1) continue;
int &res = dp[i|(1<<(k-1))][k];
res = min(res, dp[i][j] + dis[j][k]);
}
}
}
int res = inf;
for(int i=1;i<=n;i++)
res = min(res, dp[(1<<n)-1][i]);
printf("%d\n", res);
}
}
int main() {
while(~scanf("%d%d",&n,&m)){
if(n == 0 && m == 0){
break;
}
AC::init();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
AC::insert(s, i, i);
}
for(int i=1;i<=m;i++){
scanf("%s",s+1);
AC::insert(s, -1, -1);
}
AC::build();
AC::bfs(0);
for(int i=1;i<=n;i++) AC::bfs(i);
AC::solve();
}
return 0;
}
自动机上面DP,和前面的题目基本一样
const int N = 500 + 5;
const int M = 55;
int n, m, k;
int a[N];
double x[M], y[M];
double d[55][55];
namespace AC{
int tr[N][55],tot;
int e[N],fail[N];
int vis[N][55];
double res[N][55];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
memset(vis, 0, sizeof vis);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(int *s, int len){
int u = 0;
for(int i=1;i<=len;i++){
if(!tr[u][s[i]]){
tr[u][s[i]] = ++tot;
}
u = tr[u][s[i]];
}
e[u] ++;
}
void build(){
for(int i=1;i<=n;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] |= e[fail[u]];
for(int i=1;i<=n;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
double get(int u, int pos){
if(e[u]) return 1e15;
if(pos == n) return 0;
if(vis[u][pos]) return res[u][pos];
vis[u][pos] = 1;
double &ans = res[u][pos];
ans = 1e15;
for(int i=pos+1;i<=n;i++){
ans = min(ans, d[pos][i] + get(tr[u][i], i));
}
return ans;
}
}
double S(double x){ return x * x;}
int main() {
while(~scanf("%d%d",&n,&m)){
if(n == 0 && m == 0) break;
AC::init();
for(int i=1;i<=n;i++){
scanf("%lf%lf",&x[i], &y[i]);//一开始就要输入double,因为下面计算距离时,有可能爆LL
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j] = sqrt(S(x[i]-x[j]) + S(y[i]-y[j]));
}
}
for(int i=1;i<=m;i++){
scanf("%d",&k);
for(int j=1;j<=k;j++)scanf("%d",&a[j]);
AC::insert(a, k);
}
AC::build();
double res = AC::get(AC::tr[0][1], 1);
if(res == 1e15)puts("Can not be reached!");
else
printf("%.2f\n", res);
}
return 0;
}
题意:
给出\(m\)个长度小于等于\(10\)的字符串,然后求长度为\(n(n\le 25)\)的字符串,要求其最少包含\(m\)个字符串中的\(k\)个(可以有覆盖部分),问这样的字符串有多少个。
分析:
d[i][j][k]
表示长度为 i,在 j 结点,包含字符串状态为 k 的种类数
考虑 AC自动机上 j 结点的转移,u = tr[j][c]
, e[u]
表示u结点的匹配集合,则有:
d[i+1][u][k|e[u]] += d[i][j][k]
const int N = 100 + 5;
const int mod = 20090717;
int n, m, k;
char s[N];
int d[26][N][1<<10], cnt[1<<10];
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s, int id){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
if(!tr[u][s[i] - 'a']){
tr[u][s[i] - 'a'] = ++tot;
}
u = tr[u][s[i] - 'a'];
}
e[u] |= 1 << (id-1);
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] |= e[fail[u]];
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
memset(d, 0, sizeof d);
d[0][0][0] = 1;
//25 * 100 * 1024 * 26
for(int i=0;i<n;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<(1<<m);k++){
//很强大的优化
if(d[i][j][k] == 0) continue;
for(int c = 0; c < 26; c++){
int u = tr[j][c];
int &res = d[i+1][u][k|e[u]];
res = (res + d[i][j][k]) % mod;
}
}
}
}
int res = 0;
for(int i=0;i<(1<<m);i++){
if(cnt[i] < k) continue;
for(int j=0;j<=tot;j++){
res = (res + d[n][j][i]) % mod;
}
}
printf("%d\n", res);
}
}
int main() {
for(int i=0;i<(1<<10);i++){
for(int j=0;j<10;j++){
if(i >> j & 1) cnt[i]++;
}
}
while(~scanf("%d%d%d",&n,&m,&k)){
if(n == 0 && m == 0 && k == 0) break;
AC::init();
for(int i=1;i<=m;i++){
scanf("%s",s+1);
AC::insert(s, i);
}
AC::build();
AC::solve();
}
return 0;
}
题意:
DNA序列,仅包含"ACGT"四种字符,给 \(N(N\le 50)\) 个串,每个串长度不超过10,最后给出一个字符串S\((|S| \le 40)\), 求将 S 重新排列之后,最多能够包含N个串中的多少个?(允许覆盖)
分析:
重组串长度不超过40,字符种类数为4,如果用d[i][j][k][l][m]
来表示匹配到 i 号结点时,有 j 个 ‘A‘, k 个 ‘C‘, l 个 ‘G‘, m 个 ‘T‘时,最多包含几个串
枚举 i 的转移,u=tr[i][c]
, 若 c 表示 ‘A‘, 则 d[u][j+1][k][l][m] = max(d[u][j+1][k][l][m], d[i][j][k][l][m] + e[u])
const int N = 500 + 5;
int n;
char s[N];
int f[4];
inline int xid(char ch) {
if(ch == 'A') return 0;
if(ch == 'C') return 1;
if(ch == 'G') return 2;
if(ch == 'T') return 3;
}
namespace AC{
int tr[N][4],tot;
int e[N],fail[N];
queue<int> q;
vector<vector<vector<vector<int>>>> d[N];
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = xid(s[i]);
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u]++;
}
void build(){
for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] += e[fail[u]];
for(int i=0;i<4;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
memset(f, 0, sizeof f);
int len = strlen(s+1);
for(int i=1;i<=len;i++){
f[xid(s[i])] ++;
}
for(int i = 0; i <= tot; i++){
d[i].clear();
d[i].resize(f[0]+1);
for(int j = 0; j <= f[0]; j++){
d[i][j].resize(f[1] + 1);
for(int k = 0; k <= f[1]; k++){
d[i][j][k].resize(f[2]+1);
for(int l = 0; l <= f[2]; l++){
d[i][j][k][l].resize(f[3]+1, -1);
}
}
}
}
d[0][0][0][0][0] = 0;
int res = 0;
for(int num = 0; num <= len; num ++){
for(int i = 0; i <= tot; i++){
for(int j = 0; j <= f[0] && j <= num; j++){
for(int k = 0; k <= f[1] && j + k <= num; k++){
for(int l = 0; l <= f[2] && j + k + l <= num; l++){
int m = num - j - k - l;
if(m > f[3] || d[i][j][k][l][m] == -1) continue;
if(j < f[0]) d[tr[i][0]][j+1][k][l][m] = max(d[tr[i][0]][j+1][k][l][m], d[i][j][k][l][m] + e[tr[i][0]]);
if(k < f[1]) d[tr[i][1]][j][k+1][l][m] = max(d[tr[i][1]][j][k+1][l][m], d[i][j][k][l][m] + e[tr[i][1]]);
if(l < f[2]) d[tr[i][2]][j][k][l+1][m] = max(d[tr[i][2]][j][k][l+1][m], d[i][j][k][l][m] + e[tr[i][2]]);
if(m < f[3]) d[tr[i][3]][j][k][l][m+1] = max(d[tr[i][3]][j][k][l][m+1], d[i][j][k][l][m] + e[tr[i][3]]);
if(num == len);
res = max(res, d[i][j][k][l][m]);
}
}
}
}
}
printf("%d\n", res);
}
}
int main() {
int cas = 0;
while(~scanf("%d",&n)){
if(n == 0) break;
AC::init();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
AC::insert(s);
}
AC::build();
scanf("%s", s+1);
printf("Case %d: ", ++cas);
AC::solve();
}
return 0;
}
参考题解:https://blog.csdn.net/morgan_xww/article/details/7834801
const int N = 100 + 5;
const int mod = 100000;
int n, m;
char s[N];
struct mat{
int r, c;
ll s[N][N];
mat(int r=0,int c=0):r(r),c(c){
memset(s, 0, sizeof s);
}
};
mat operator*(const mat&a, const mat&b){
mat c = mat(a.r, b.c);
for (int i = 0; i < c.r;i++){
for (int j = 0; j < c.c;j++)
for (int k = 0; k < a.c;k++)
c.s[i][j] = (c.s[i][j] + a.s[i][k] * b.s[k][j]) % mod;
}
return c;
}
mat power(mat a,int b){
mat res = a;
b--;
for (; b;b>>=1){
if(b & 1)
res = res * a;
a = a * a;
}
return res;
}
inline int xid(char ch){
if(ch == 'A') return 0;
if(ch == 'C') return 1;
if(ch == 'G') return 2;
if(ch == 'T') return 3;
}
namespace AC{
int tr[N][4],tot;
int e[N],fail[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = xid(s[i]);
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u] ++;
}
void build(){
for(int i=0;i<4;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] += e[fail[u]];
for(int i=0;i<4;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
mat t(tot+1, tot+1);
for(int i=0;i<=tot;i++){
if(e[i]) continue;
for(int j=0;j<4;j++){
int u = tr[i][j];
if(e[u]) continue;
t.s[i][u] ++;
}
}
t = power(t, n);
int res = 0;
for(int i=0;i<=tot;i++) res = (res + t.s[0][i]) % mod;
printf("%d\n", res);
}
}
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++){
scanf("%s", s+1);
AC::insert(s);
}
AC::build();
AC::solve();
return 0;
}
和上个题基本一样
const int N = 30 + 5;
int m;
ll n;
char s[N];
struct mat{
int r, c;
ull s[N][N];
mat(int r=0,int c=0):r(r),c(c){
memset(s, 0, sizeof s);
}
void print(){
for(int i=0;i<r;i++){
for(int j=0;j<c;j++){
printf("%llu ", s[i][j]);
}
puts("");
}
}
}one;
mat operator*(const mat&a, const mat&b){
mat c = mat(a.r, b.c);
for (int i = 0; i < c.r;i++){
for (int j = 0; j < c.c;j++)
for (int k = 0; k < a.c;k++)
c.s[i][j] = c.s[i][j] + a.s[i][k] * b.s[k][j];
}
return c;
}
mat operator+(const mat&a, const mat&b){
mat c = mat(a.r, b.c);
for (int i = 0; i < c.r;i++){
for (int j = 0; j < c.c;j++){
c.s[i][j] = a.s[i][j] + b.s[i][j];
}
}
return c;
}
mat power(mat a,ll b){
mat res = a;
b--;
for (; b;b>>=1){
if(b & 1)
res = res * a;
a = a * a;
}
return res;
}
mat getsum2(mat a, ll b){
if(b == 0) return one;
if(b == 1) return a;
if(b & 1){
return (one + power(a, (b+1)/2)) * getsum2(a, b / 2) + power(a, (b+1)/2);
}
else{
return (one + power(a, b/2)) * getsum2(a, b / 2);
}
}
ull ksm(ull a, ull b){
ull res = 1;
for(;b;b>>=1){
if(b & 1) res = res * a;
a = a * a;
}
return res;
}
ull getsum(ull a, ull b){
if(b == 0) return 1;
if(b == 1) return a;
if(b & 1){
return (ksm(a, (b+1)/2) + 1) * getsum(a, b / 2) + ksm(a, (b+1)/2);
}
else{
return (ksm(a, b/2) + 1) * getsum(a, b / 2);
}
}
namespace AC{
int tr[N][26],tot;
int e[N],fail[N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = s[i] - 'a';
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u] ++;
}
void build(){
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] += e[fail[u]];
for(int i=0;i<26;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
mat t(tot+1, tot+1);
one = mat(tot+1, tot+1);
for(int i=0;i<=tot;i++)one.s[i][i] = 1;
for(int i=0;i<=tot;i++){
if(e[i]) continue;
for(int j=0;j<26;j++){
int u = tr[i][j];
if(e[u]) continue;
t.s[i][u] ++;
}
}
t = getsum2(t, n);
ull res = 0;
for(int i=0;i<=tot;i++) res += t.s[0][i];
printf("%llu\n", getsum(26, n) - res);
}
}
int main(){
while(~scanf("%d%lld",&m,&n)){
AC::init();
for(int i=1;i<=m;i++){
scanf("%s", s+1);
AC::insert(s);
}
AC::build();
AC::solve();
}
return 0;
}
d[i][j]
表示长度为 i,到达 j 号节点的状态
if(e[u] == 0){
d[i+1][u] += d[i][j];// u = tr[j][c];
}
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 100 + 5;
const int base = 10;
int n, m, p;
int xid[300];
char s[N], a[N];
int get(char ch){
for(int i=1;i<=m;i++){
if(ch == a[i]) return i-1;
}
}
struct BigInt
{
int v[105], len;
BigInt(int r = 0)
{
memset(v, 0, sizeof(v));
for(len = 0; r > 0; r /= base) v[len++] = r % base;
}
BigInt operator + (const BigInt &a)
{
BigInt ans;
int i , c = 0;
for(i = 0; i < len || i < a.len || c > 0; i++)
{
if(i < len)c += v[i];
if(i < a.len)c += a.v[i];
ans.v[i] = c % base;
c /= base;
}
ans.len = i;
return ans;
}
void print()
{
printf("%d", len == 0 ? 0 : v[len - 1]);
for(int i = len - 2; i >= 0; i--)
printf("%d", v[i]);
printf("\n");
}
};
namespace AC{
int tr[N][55],tot;
int e[N],fail[N];
BigInt d[55][N];
int vis[55][N];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = get(s[i]);
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u] ++;
}
void build(){
for(int i=0;i<m;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] += e[fail[u]];
for(int i=0;i<m;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
d[0][0] = BigInt(1);
for(int i = 0;i < n; i++){
for(int u = 0; u <= tot; u++){
if(e[u]) continue;
for(int c = 0; c < m; c++){
int v = tr[u][c];
if(!e[v]){
d[i+1][v] = d[i+1][v] + d[i][u];
}
}
}
}
BigInt res;
for(int i=0;i<=tot;i++){
res = res + d[n][i];
}
res.print();
}
}
int main(){
scanf("%d%d%d",&m,&n,&p);
scanf("%s", a+1);
for(int i=1;i<=p;i++){
scanf("%s", s+1);
AC::insert(s);
}
AC::build();
AC::solve();
return 0;
}
d[u][i][j][k]
表示走到 \(u\) 节点,走了$ i$ 次 ‘D‘, \(j\) 次 ‘R‘, 经过的模板串状态为 \(k\)时的方案数
const int N = 200 + 5;
const int mod = 1000000007;
int n, m;
char s[N];
inline xid(char ch){
if(ch == 'R') return 1;
else return 0;
}
namespace AC{
int tr[N][2],tot;
int e[N],fail[N];
int d[N][105][105][4];
bool vis[N][105][105][4];
queue<int> q;
void init(){
for(int i=0;i<=tot;i++){
memset(tr[i],0,sizeof tr[i]);
fail[i] = e[i] = 0;
}
tot = 0;
}
void insert(char *s, int id){
int u = 0;
int len = strlen(s + 1);
for(int i=1;i<=len;i++){
int c = xid(s[i]);
if(!tr[u][c]){
tr[u][c] = ++tot;
}
u = tr[u][c];
}
e[u] |= 1 << id;
}
void build(){
for(int i=0;i<2;i++)if(tr[0][i])q.push(tr[0][i]);
while(q.size()){
int u = q.front();q.pop();
e[u] |= e[fail[u]];
for(int i=0;i<2;i++){
if(tr[u][i])fail[tr[u][i]] = tr[fail[u]][i],q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void solve(){
memset(d, 0, sizeof d);
memset(vis, 0, sizeof vis);
d[0][0][0][0] = 1;
vis[0][0][0][0] = 1;
for(int i=0; i <= n; i++){
for(int j=0; j<= m; j++){
for(int u = 0; u<= tot; u++){
for(int k = 0; k < 4; k++){
if(vis[u][i][j][k] == 0) continue; // 如果该点压根就到不了,就不要参与转移
if(i < n){
int v = tr[u][0];
vis[v][i + 1][j][k | e[v]] = 1;
(d[v][i + 1][j][k | e[v]] += d[u][i][j][k]) %= mod;
}
if(j < m){
int v = tr[u][1];
vis[v][i][j + 1][k | e[v]] = 1;
(d[v][i][j + 1][k | e[v]] += d[u][i][j][k]) %= mod;
}
}
}
}
}
ll res = 0;
for(int i=0; i<=tot; i++){
(res += d[i][n][m][3]) %= mod;
}
printf("%lld\n", res);
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d", &m, &n);
AC::init();
scanf("%s", s+1);
AC::insert(s, 0);
scanf("%s", s+1);
AC::insert(s, 1);
AC::build();
AC::solve();
}
return 0;
}
标签:ref 位置 i++ details 防止 遇到 大小写 ack lin
原文地址:https://www.cnblogs.com/1625--H/p/12532624.html