Asia Hong Kong Regional Contest 2016 J Taboo(level 3)(ac自动机+dfs/dp)

题目链接

题意:

给你n个串,让你找出最长的串s,使得这n个串都不是s的子串。串只由0,1组成

如果串可以是无穷长,输出-1

解析:

一开始看的时候没有怎么深入想,后来赛后看了题解。发现是ac自动机,后来看自己以前做的ac自动机的题目,

发现有做到过类似的....都是给你n个串,让你构造不包含这个n个串的一个串。

这道题构造出ac自动机,你在ac自动机上跑就可以了。

一开始还没搞清楚ac自动机的两个版本的区别——空孩子(字符表示为c)不指向null,指向父节点的fail指针的那条链上,

第一个孩子节点字符表示为c的孩子节点 和空孩子直接指向null的版本。

这道题你要用第一个空孩子不指向null的版本,因为当你匹配串时,进入了一个空孩子,这并不是代表串可以无穷了

,因为后面的串可能还会包含n个串里面的某一个,所以就需要继续去匹配。我一开始就是这里没搞清楚一直再WA

那怎么判断串可以无穷呢,就是成环。你从一个节点往下dfs,最后又回到这个点,那么说明就成环了。这个你用vis[]

标记一下就可以了。不过我这里一开始这样写T了。因为有两个地方写得太LJ了。

1.一个是在x,需要往下遍历他的孩子时,首先要判断当前状态+他的孩子会不会包含一个串的结尾。所以就需要从他的孩子

的fail出发,一直到root,如果中间有碰到一个点是串的结尾(end>0)说明会有这种情况,那么这个孩子就不能遍历了。

我这里是dfs的时候,判断一遍孩子,就遍历一遍他的fail链,其实这个完全可以在构造点的fail链的时候通过递推传递到每一个点

这样,dfs的时候只需要O(1)判断当前孩子的end>0就可以了

2.在记录答案字符串的时候,我写的是每当你可以往下走,并且长度>len,那么就重新把当前表示的字符串赋给答案字符串。

这样的话,如果答案串的长度为2e5,那么在找答案串的过程中,长度每+1,那么就需要重新复制一遍,复杂度就为O(n*(n-1)/2)

=O(n*n)。。。。。。。后来发现这里写傻了..我改成每到一个点,他不能再往下遍历了,并且长度>len,就把串赋给答案串

这样复杂度会好一点。

网上看到大佬用dp数组来存的,就完全没有这种问题。当dfs一遍后,dp数组算出来,只要从dp根节点出发,

dp[son[x][i]]+1==dp[x]就输出i,往son[x][i]这边走。

其实我后面写的那个vis[x]记录从x点出发,最多能得到的串的长度的版本从也是跟dp那个类似的,这个版本我还加了剪枝。

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//const int N = 1e3+100;
const int MAX = 3e5+100;
const int C = '0';
const int NUM = 2;


struct Tree//字典树
{
    int fail;//失配指针
    int vis[NUM];//子节点的位置
    int end;//标记有几个单词以这个节点结尾
    int id;
}AC[MAX];//Trie树

set res;
int cnt=0;//Trie的指针

int newnode()
{
	++cnt;
	AC[cnt].end=0;
	for(int i=0;i Q;//队列
    for(int i=0;i=3) break;
        }
        if(res.size()>=3) break;
    }
    if(res.size()) {
        printf("web %d:", id);
        for (auto v:res) {
            printf(" %d", v);
        }
        printf("\n");
    }
    return res.size()>0?1:0;

}

int len=0;
int conf;
char ans[2][MAX];
int vis[MAX];
int dfs(int x,int dep)
{
	if(vis[x]) return 1;
	int now=0;
	int flag=1;
	vis[x]=1;
	int tot=0;
	for(int i=0;i<2;i++)
	{
		flag=1;
		now=i;
		/*for(int t=AC[x].vis[now];t;t=AC[t].fail)
		{
			if(AC[t].end>0) {flag=0;break;}
		}*/
		int t=AC[x].vis[now];
		if(t&&AC[t].end>0) {flag=0;}
		if(flag)
		{
			ans[conf][dep]=now+'0';

			flag=dfs(AC[x].vis[now],dep+1);
			if(flag) return 1;
		}
		tot|=flag;
	}
	if(!tot&&dep>len)
    {
        len=dep;
        for(int j=0;j0) {flag=0;break;}
		}
		if(flag)
		{
			ans[conf][dep]=now+'0';
			if(dep+1>len)
			{
				len=dep+1;
				for(int i=0;i

 

vis[]做dp

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//const int N = 1e3+100;
const int MAX = 3e5+100;
const int C = '0';
const int NUM = 2;
const int INF = 0X3F3F3F3F;

struct Tree//字典树
{
    int fail;//失配指针
    int vis[NUM];//子节点的位置
    int end;//标记有几个单词以这个节点结尾
    int id;
}AC[MAX];//Trie树

set res;
int cnt=0;//Trie的指针

int newnode()
{
	++cnt;
	AC[cnt].end=0;
	for(int i=0;i Q;//队列
    for(int i=0;i=3) break;
        }
        if(res.size()>=3) break;
    }
    if(res.size()) {
        printf("web %d:", id);
        for (auto v:res) {
            printf(" %d", v);
        }
        printf("\n");
    }
    return res.size()>0?1:0;

}

int len=0;
int conf;
char ans[2][MAX];
int vis[MAX];  //vis[x]表示从x向下走,不包括x最长的串




int dfs(int x,int dep)   //x可以走,并且dep已经包括x
{
	if(vis[x]!=-1) 
	{
		//return dep+vis[x];
		if(vis[x]>=INF) return INF;
		else if(vis[x]+dep<=len) return vis[x]+1;
	}
	int now=0;
	int flag=1;
	vis[x]=INF;
	int res=0;
	int tot=0;
	for(int i=0;i<2;i++)
	{
		flag=1;
		now=i;
		/*for(int t=AC[x].vis[now];t;t=AC[t].fail)
		{
			if(AC[t].end>0) {flag=0;break;}
		}*/
		int t=AC[x].vis[now];
		if(t&&AC[t].end>0) {flag=0;}
		if(flag)
		{
			//res=max(res,1);
			ans[conf][dep]=now+'0';
			
			res=max(res,dfs(AC[x].vis[now],dep+1));
			if(res>=INF) return INF;
		}
		tot|=flag;
	}
	if(!tot&&dep>len)
	{
		len=dep;
		for(int j=0;j0) {flag=0;break;}
		}
		if(flag)
		{
			ans[conf][dep]=now+'0';
			if(dep+1>len) 
			{
				len=dep+1;
				for(int i=0;i=INF)
	{
		printf("-1\n");
		return 0;
	}
	for(int i=0;i

 

你可能感兴趣的:(ac自动机,ACM)