题意:给定n个禁止串,只要求用前m个字母,问能否构造出一个无限长的字符串,其不包含任何一个禁止串并且没有循环节。
如果是,输出Yes,否则输出No.
思路:
首先,禁止串。看到禁止串就想起了之前没过的那个AC自动机+矩阵快速幂的病毒串。所以可以联想到是先构建禁止串的AC自动机,然后再建图。
然后就是朴素的trie树+AC自动机构造。
建图一定是要求两头都没有禁止串标记才可以。
//build graph
for(int i = 0;i <= tot;i ++)
{
if(sum[i]) continue;
for(int j = 0;j < m;j ++)
{
if(sum[trie[i][j]]) continue;
// printf("jianbian : %d -> %d\n",i,trie[i][j]);
build(i,trie[i][j]);
}
}
此时判断是不是能够成一个无限长的串知道了(即有没有环,环是首尾相接的,这个环在图中就一定可以无限重复)
那么怎么判断是不是有循环节呢?
答案就是找一个强联通分量里是不是有多个环。
最常见的情况:两个字母 一个a,一个b,假设最终构建的图中有一个a的自环,然后a,b互相连通。
那么我可以构造一个字符串ababbabbbabbbbab......无限循环没有循环节。
同一个强联通分量里有两个以上的环就可以了。
具体判断方法很简单,先find_scc找强联通分量,然后再对每个强连通分量搜索,如果某个点能直接连两个以上与它在同一个强联通分量内的点,那么答案就是Yes,否则就是No.
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<ctime>
#define up(i,x,y) for(int i = x;i <= y;i ++)
#define down(i,x,y) for(int i = x;i >= y;i --)
#define mem(a,b) memset((a),(b),sizeof(a))
#define mod(x) ((x)%MOD)
#define lson p<<1
#define rson p<<1|1
using namespace std;
typedef long long ll;
const int SIZE = 100010;
const int INF = 2147483640;
const double eps = 1e-8;
int n,m;
int trie[SIZE][26],l,root,tot;
bool sum[SIZE];
int fail[SIZE];
//trie字典树 sum标记单词结尾以及个数
void insert(char s[])
{
root = 0;//根节点为0节点
int l = strlen(s);
for(int i = 0;i < l;i ++)
{
int x = s[i] - 'a';//每次看看它是否已经有了这个节点
if(!trie[root][x])
{
trie[root][x] = ++tot;//如果没有就新建
mem(trie[tot],0);
fail[tot] = 0;
sum[tot] = 0;
}
root = trie[root][x];//如果有就直接往下走
}
sum[root] = 1;
}
//每个节点的失配指针指向的是以 当前节点表示的字符为最后一个字符的 最长当前字符串的 后缀字符串的 最后一个节点。
queue <int> q;
void build_ac()
{
fail[0] = 0;//根节点的失败指针指向本身
for(int i = 0;i < m;i ++)
{
int u = trie[0][i];
if(u)
{
q.push(u);
fail[u] = 0;//根节点指向的节点的fail指针只可能指向根节点
}
else
{
trie[0][i] = 0;
}
}
while(!q.empty())
{
int f = q.front();//取队首
q.pop();
sum[f] |= sum[fail[f]];
for(int i = 0;i < m;i ++)
{
int u = trie[f][i];
if(!u)
{
trie[f][i] = trie[fail[f]][i];方便求fail
}
else
{
q.push(u);
int v = fail[f];//父亲的fail指针
fail[u] = trie[v][i];
//注意 此时failu变成了 与父亲相同字母的fail指针的 它的儿子字母为i的 点的编号
}
}
}
}
struct Edge
{
int from,to,bh;
}edges[SIZE*26];
int head[SIZE],nextt[SIZE*26],tot_edge;
void build(int f,int t)
{
edges[++tot_edge].to = t;
edges[tot_edge].from = f;
edges[tot_edge].bh = tot_edge;
nextt[tot_edge] = head[f];
head[f] = tot_edge;
}//建图
void clear_ac(int x)
{
for(int i = 0;i < 26;i ++)
{
if(trie[x][i])
{
clear_ac(trie[x][i]);
trie[x][i] = 0;
}
}
}
stack <int> s;
int dfs_clock,low[SIZE],pre[SIZE],scc[SIZE],scc_cnt;
int tarjan(int u)
{
low[u] = pre[u] = ++ dfs_clock;
s.push(u);
for(int i = head[u] ; i ; i = nextt[i])
{
int v = edges[i].to;
if(!pre[v])
{
low[v] = tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(!scc[v])
{
low[u] = min(low[u],pre[v]);
}
}
if(pre[u] == low[u])
{
scc_cnt ++;
while(12 < 450)
{
int f = s.top();
s.pop();
scc[f] = scc_cnt;
if(f == u)
break;
}
}
return low[u];
}
void find_scc()
{
dfs_clock = scc_cnt = 0;
memset(pre,0,sizeof(pre));
memset(low,0,sizeof(low));
for(int i = 0;i <= tot;i ++)
{
if(!pre[i]) tarjan(i);
}
}
bool judge()
{
bool flag = 0;
int cnt;
for(int i = 0;i <= tot;i ++)
{
cnt = 0;
flag = 0;
for(int j = head[i];j;j = nextt[j])
{
int v = edges[j].to;
if(scc[i] == scc[v]) cnt ++;
if(i == v)
{
// printf("nmd? %d %d bh:%d\n",i,v,edges[j].bh);
flag = 1;
}
if(cnt >= 2 && flag)
{
// printf("wsm???????????????????? %d\n",i);
return 1;
}
}
}
return 0;
}
void init()
{
//up(i,0,SIZE-1) mem(trie[i],0);//沙比才用memset
// clear_ac(0);
// printf("qaq\n");
mem(sum,0);
// mem(fail,0);//不用清
tot = 0;
//前向星
mem(head,0);
// mem(edges,0);
// mem(nextt,0);
memset(scc,0,sizeof(scc));
while(!s.empty())
s.pop();
tot_edge = 0;//边
//trie根节点
mem(trie[0],0);
fail[0] = 0;
sum[0] = 0;
}
char ss[2333];
int main(int argc, char const *argv[])
{
int t;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
up(i,1,n)
{
scanf("\n%s",ss);
insert(ss);
}
build_ac();
//build graph
for(int i = 0;i <= tot;i ++)
{
if(sum[i]) continue;
for(int j = 0;j < m;j ++)
{
if(sum[trie[i][j]]) continue;
// printf("jianbian : %d -> %d\n",i,trie[i][j]);
build(i,trie[i][j]);
}
}
find_scc();
/* for(int i = 1;i <= tot_edge;i ++)
{
printf("%d -> %d\n",edges[i].from,edges[i].to);
}
for(int i = 0;i <= tot;i ++) {printf("scc %d:%d\n",i,scc[i]);}
*/ if(judge()) printf("Yes\n");
else printf("No\n");
}
return 0;
}
/*
2
1 2
aa
2 2
aa
bb
*/