• [CFGYM100221C]Forbidden Subwords


    XII.[CFGYM100221C]Forbidden Subwords

    ”双向无限“之类东西太抽象了,我们不妨从简单点的地方考虑。比如说,有限串。

    一个有限串中没有出现任何禁忌串的充要条件是什么?沿着AC自动机上边走时没有碰到任何结束点。

    现在,我们考虑一个右侧无限的串(即有一个明确开头,但另一端没有结尾的串)。它不出现禁忌串的充要条件,依然是不经过结束点。

    我们考虑将AC自动机中所有的结束点删去,仅保留非结束点。明显,剩下的图仍是一张有向图。

    考虑对该有向图进行强连通分量染色。考虑染色后的一个SCC。

    明显,其中必然存在至少一个简单有向环。

    假如该有向环上还存在至少一条非环边的话,我们考虑一条单向无限的串,它从环上一点出发,并不断绕着它转圈。在某几圈里,我们绕着环走完整一圈;在某几圈里,我们直接走捷径从非环边直接插过去。明显,这样可以生成无限条合法单向无限串,于是答案就直接是 \(-1\)

    否则,即所有强连通分量要么是简单环,要么是一个点。考虑缩点,生成一张DAG。我们把DAG上环缩成的点称作”环点“,其余点称作”单点“。

    我们考虑若在DAG上,一个环点可以走到另一个环点,会发生什么:它可以在起始环点绕一圈,然后走到终止环点;也可以绕两圈,走到终止环点;三圈、四圈、五圈、更多圈……因此,我们发现,这样也会生成无限条合法串。

    明显,除此之外,再无答案无限的情形。

    现在我们考虑双向无限串。明显,若图中仍存在一非环SCC,则上述关于非环边的推断仍然成立,答案仍为无限。

    否则,考虑一条双向无限串在DAG上的表现:其一定是在一个环点上绕无数圈,然后走一条路径到另一个环点,然后继续绕无数圈。

    但是,若这条路径上还存在另一个环点,则在途径的这个环点,我们可以选择绕一圈、两圈、更多圈……因此答案为无穷。

    也就是说,不能有一条路径经过三个及以上环点。这个很好判断。

    否则,就直接求出从一个环点到另一个环点的路径数即可。这个随便拓扑一下就出来了。

    时间复杂度 \(O\Big(|\Sigma|\sum|s|\Big)\),其中 \(|\Sigma|\) 是字符集大小。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int m,n,cnt=1,dfn[100100],low[100100],tot,col[100100],c,sz[100100];
    struct AC_Automaton{
    	int ch[6],fail;
    	bool fob;
    }t[100100];
    char s[20];
    void ins(){
    	scanf("%s",s);int S=strlen(s);
    	int x=1;
    	for(int i=0;i<S;i++){
    		if(!t[x].ch[s[i]-'a'])t[x].ch[s[i]-'a']=++cnt;
    		x=t[x].ch[s[i]-'a'];
    	}
    	t[x].fob=true;
    }
    queue<int>q;
    void build(){
    	for(int i=0;i<m;i++){
    		if(t[1].ch[i])t[t[1].ch[i]].fail=1,q.push(t[1].ch[i]);
    		else t[1].ch[i]=1;
    	}
    	while(!q.empty()){
    		int x=q.front();q.pop();
    		for(int i=0;i<m;i++){
    			if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],t[t[x].ch[i]].fob|=t[t[t[x].fail].ch[i]].fob,q.push(t[x].ch[i]);
    			else t[x].ch[i]=t[t[x].fail].ch[i];
    		}
    	}
    }
    stack<int>stk;
    void Tarjan(int x){
    	dfn[x]=low[x]=++tot,stk.push(x);
    	for(int i=0;i<m;i++){
    		if(t[t[x].ch[i]].fob)continue;
    		if(!low[t[x].ch[i]])Tarjan(t[x].ch[i]),low[x]=min(low[x],low[t[x].ch[i]]);
    		else if(!col[t[x].ch[i]])low[x]=min(low[x],dfn[t[x].ch[i]]);
    	}
    	if(low[x]<dfn[x])return;
    	c++;
    	while(stk.top()!=x)col[stk.top()]=c,sz[c]++,stk.pop();
    	col[stk.top()]=c,sz[c]++,stk.pop();
    }
    vector<int>v[100100];
    bool cir[100100];
    int dep[100100],in[100100];
    ll f[100100],res;
    int main(){
    	freopen("forbidden.in","r",stdin);
    	freopen("forbidden.out","w",stdout);
    	scanf("%d%d",&m,&n);
    	for(int i=1;i<=n;i++)ins();
    	build();
    	for(int i=1;i<=cnt;i++)if(!t[i].fob&&!dfn[i])Tarjan(i); 
    	for(int i=1;i<=cnt;i++)for(int j=0;j<m;j++){
    		if(t[i].fob||t[t[i].ch[j]].fob)continue;
    		if(col[i]==col[t[i].ch[j]])sz[col[i]]--;
    		else v[col[t[i].ch[j]]].push_back(col[i]);
    	}
    	for(int i=1;i<=c;i++){
    		if(sz[i]<0){puts("-1");return 0;}
    		cir[i]=!sz[i];
    	}
    	for(int i=1;i<=c;i++)for(auto j:v[i])in[j]++;
    	for(int i=1;i<=c;i++)if(!in[i])q.push(i);
    	while(!q.empty()){
    		int x=q.front();q.pop();
    		dep[x]+=cir[x],f[x]+=cir[x];
    		if(cir[x])res+=f[x];
    		if(dep[x]>=3){puts("-1");return 0;}
    		for(auto y:v[x]){dep[y]=max(dep[y],dep[x]),f[y]+=f[x];if(!--in[y])q.push(y);}
    	}
    	printf("%lld\n",res);
    	return 0;
    }
    
  • 相关阅读:
    python_并发编程——数据共享
    python_并发编程——管道
    python_并发编程——消费者和生产者模型
    python_并发编程——队列
    python_并发编程——事件
    python_并发编程——锁
    python_并发编程——守护进程
    面试题-3
    CentOS7使用‘中科大源’
    date命令查看与修改
  • 原文地址:https://www.cnblogs.com/Troverld/p/14596734.html
Copyright © 2020-2023  润新知