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<iostream>
#include<cstdio>
#include<cstdlib>
#include<set>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
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<int> res;
int cnt=0;//Trie的指针

int newnode()
{
	++cnt;
	AC[cnt].end=0;
	for(int i=0;i<NUM;i++)
		AC[cnt].vis[i]=0;
	AC[cnt].fail=0;
	return cnt;
}
inline void Build(char *s,int id)
{
    int l=strlen(s);
    int now=0;//字典树的当前指针
    for(int i=0;i<l;++i)//构造Trie树
    {
        if(AC[now].vis[s[i]-C]==0)//Trie树没有这个子节点
            AC[now].vis[s[i]-C]=newnode();//构造出来
        now=AC[now].vis[s[i]-C];//向下构造
    }
    AC[now].end+=1;//标记单词结尾
    AC[now].id=id;
}
void Get_fail()//构造fail指针
{
    queue<int> Q;//队列
    for(int i=0;i<NUM;++i)//第二层的fail指针提前处理一下
    {
        if(AC[0].vis[i]!=0)
        {
            AC[AC[0].vis[i]].fail=0;//指向根节点
            Q.push(AC[0].vis[i]);//压入队列
        }
    }
    while(!Q.empty())//BFS求fail指针
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<NUM;++i)//枚举所有子节点
        {
            if(AC[u].vis[i]!=0)//存在这个子节点
            {
                AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
				AC[AC[u].vis[i]].end+=AC[AC[AC[u].fail].vis[i]].end;
                //子节点的fail指针指向当前节点的
                //fail指针所指向的节点的相同子节点
                Q.push(AC[u].vis[i]);//压入队列
            }
            else//不存在这个子节点
			{
                AC[u].vis[i]=AC[AC[u].fail].vis[i];
			}
            //当前节点的这个子节点指向当
            //前节点fail指针的这个子节点
        }
    }
}
int AC_Query(char *s,int id)//AC自动机匹配
{
    res.clear();
    int l=strlen(s);
    int now=0,ans=0;
    for(int i=0;i<l;++i)
    {
        now=AC[now].vis[s[i]-C];//向下一层
        for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)//循环求解
        {
            if(AC[t].id)  res.insert(AC[t].id);
            if(res.size()>=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;j<len;j++)
            ans[conf^1][j]=ans[conf][j];
    }
	vis[x]=0;
	return flag;
	/*if(!flag)
	{
		flag=1;
		now^=1;
		for(int t=AC[x].vis[now];t;t=AC[t].fail)
		{
			if(AC[t].end>0) {flag=0;break;}
		}
		if(flag)
		{
			ans[conf][dep]=now+'0';
			if(dep+1>len)
			{
				len=dep+1;
				for(int i=0;i<len;i++)
					ans[conf^1][i]=ans[conf][i];
			}
			flag=dfs(AC[x].vis[now],dep+1);
		}
	}

	return flag;*/

}


char s[MAX];
int main()
{
    int n;

    scanf("%d",&n);
    getchar();
    cnt=0;  //!
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s);
        Build(s,i);
    }
    AC[0].fail=0;//结束标志
    Get_fail();//求出失配指针
    conf=0;
    int flag=dfs(0,0);
	if(flag)
	{
		printf("-1\n");
		return 0;
	}
	for(int i=0;i<len;i++)
		printf("%c",ans[1][i]);
	printf("\n");
    return 0;
}

 

vis[]做dp

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<set>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
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<int> res;
int cnt=0;//Trie的指针

int newnode()
{
	++cnt;
	AC[cnt].end=0;
	for(int i=0;i<NUM;i++)
		AC[cnt].vis[i]=0;
	AC[cnt].fail=0;
	return cnt;
}
inline void Build(char *s,int id)
{
    int l=strlen(s);
    int now=0;//字典树的当前指针
    for(int i=0;i<l;++i)//构造Trie树
    {
        if(AC[now].vis[s[i]-C]==0)//Trie树没有这个子节点
            AC[now].vis[s[i]-C]=newnode();//构造出来
        now=AC[now].vis[s[i]-C];//向下构造
    }
    AC[now].end+=1;//标记单词结尾
    AC[now].id=id;
}
void Get_fail()//构造fail指针
{
    queue<int> Q;//队列
    for(int i=0;i<NUM;++i)//第二层的fail指针提前处理一下
    {
        if(AC[0].vis[i]!=0)
        {
            AC[AC[0].vis[i]].fail=0;//指向根节点
            Q.push(AC[0].vis[i]);//压入队列
        }
    }
    while(!Q.empty())//BFS求fail指针
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<NUM;++i)//枚举所有子节点
        {
            if(AC[u].vis[i]!=0)//存在这个子节点
            {
                AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
				AC[AC[u].vis[i]].end+=AC[AC[AC[u].fail].vis[i]].end;
                //子节点的fail指针指向当前节点的
                //fail指针所指向的节点的相同子节点
                Q.push(AC[u].vis[i]);//压入队列
            }
            else//不存在这个子节点
			{
                AC[u].vis[i]=AC[AC[u].fail].vis[i];
			}
            //当前节点的这个子节点指向当
            //前节点fail指针的这个子节点
        }
    }
}
int AC_Query(char *s,int id)//AC自动机匹配
{
    res.clear();
    int l=strlen(s);
    int now=0,ans=0;
    for(int i=0;i<l;++i)
    {
        now=AC[now].vis[s[i]-C];//向下一层
        for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)//循环求解
        {
            if(AC[t].id)  res.insert(AC[t].id);
            if(res.size()>=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;j<len;j++)
			ans[conf^1][j]=ans[conf][j];
	}
	vis[x]=res;
	return res+1;
	/*if(!flag)
	{
		flag=1;
		now^=1;
		for(int t=AC[x].vis[now];t;t=AC[t].fail)
		{
			if(AC[t].end>0) {flag=0;break;}
		}
		if(flag)
		{
			ans[conf][dep]=now+'0';
			if(dep+1>len) 
			{
				len=dep+1;
				for(int i=0;i<len;i++)
					ans[conf^1][i]=ans[conf][i];
			}
			flag=dfs(AC[x].vis[now],dep+1);
		}
	}

	return flag;*/

}


char s[MAX];
int main()
{
    int n;
	memset(vis,-1,sizeof(vis));
    scanf("%d",&n);
    getchar();
    cnt=0;  //!
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s);
        Build(s,i);
    }
    AC[0].fail=0;//结束标志
    Get_fail();//求出失配指针
    conf=0;
    int flag=dfs(0,0);
	if(flag>=INF)
	{
		printf("-1\n");
		return 0;
	}
	for(int i=0;i<len;i++)
		printf("%c",ans[1][i]);
	printf("\n");
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值