題意:
給你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;
}