String of Infinity ZOJ - 3784 AC自动机+强连通分量

题目地址

题意:给定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

*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值