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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章