• CF1483F


    CF1483F - Exam

    题目大意

    给定(n)个不同串(s_i),令(s_isub s_j)表示(s_i)(s_j)的子串

    求所有二元组((i,j)(i e j))满足

    (s_isub s_j, exists k e i,k e j,s_isub s_ksub s_j)


    分析

    首先可以说明答案是(O(sum |s_i|))级别的

    对于每个(s_j)考虑其枚举(r)作为(s_i)的右端点,则(s_i)只有最长的一个会贡献答案

    故每个右端点最多对应一个(s_i)

    一般情况下,判断子串相同的问题我们需要( ext{SAM})

    但是鉴于这道题实际上需要判别的子串只有(n)个,而维护匹配只需要( ext{AC})自动机,所以不需要( ext{SAM})

    那么可以在( ext{AC})自动机上预处理每个匹配节点在( ext{fail})树上最近的有效祖先

    即对于每个右端点找到了最长的(s_i)

    可以对于找到的(s_i)简单去重,但是仍然存在以下多余计算

    1.(s_i)所对应的([l,r])被某一个(s_i')对应的([l',r'])包含的情况,可以(O(n))判断区间包含干掉

    2.一个(s_i)在这个位置被计算,但是实际上在其他位置被包含

    实际上,这只需要判断(s_i)每一次被出现是否都是有效的即可

    可以用树状数组+( ext{dfs})序维护(s_i)出现的次数

    时间复杂度为(O((sum |s_i|)log (sum |s_i|))),空间复杂度为(O((sum |s_i|)|Sigma|))

    似乎是Codeforces上最快的Submission?

    const int N=1e6+10,INF=1e9+10;
    
    int n;
    int fail[N],lst[N],trie[N][26],End[N],dep[N],fa[N],L[N],R[N],I[N],dfn,cnt;
    struct Edge{
    	int to,nxt;
    } e[N<<1];
    int head[N],ecnt;
    void AddEdge(int u,int v) {
    	e[++ecnt]=(Edge){v,head[u]};
    	head[u]=ecnt;
    }
    
    void dfs(int u) {
    	I[L[u]=++dfn]=u;
    	for(int i=head[u];i;i=e[i].nxt) {
    		int v=e[i].to;
    		if(!lst[v]) lst[v]=lst[u];
    		dfs(v);
    	}
    	R[u]=dfn;
    }
    
    void Build() {
    	static queue <int> que;
    	rep(i,0,25) if(trie[0][i]) que.push(trie[0][i]);
    	while(!que.empty()) {
    		int u=que.front(); que.pop();
    		AddEdge(fail[u],u);
    		rep(i,0,25) {
    			int &v=trie[u][i];
    			if(!v) v=trie[fail[u]][i];
    			else fail[v]=trie[fail[u]][i],que.push(v);
    		}
    	}
    	dfs(0);
    }
    
    struct BIT{
    	int s[N];
    	void Add(int p,int x){ while(p<=dfn) s[p]+=x,p+=p&-p; }
    	int Que(int p){
    		int res=0;
    		while(p) res+=s[p],p-=p&-p;
    		return res;
    	}
    	int Que(int l,int r){ return Que(r)-Que(l-1); }
    } T;
    
    char s[N];
    int A[N],B[N],C;
    
    int main(){
    	n=rd();
    	rep(i,1,n) {
    		scanf("%s",s+1);
    		int u=0;
    		for(int j=1;s[j];++j) {
    			int &v=trie[u][s[j]-'a'];
    			if(!v) v=++cnt,fa[v]=u,dep[v]=dep[u]+1;
    			u=v;
    		}
    		lst[End[i]=u]=u;
    	}
    	Build();
    	int ans=0;
    	rep(i,1,n) {
    		C=0;
    		for(int u=End[i],mi=1e9;u;u=fa[u]) {
    			T.Add(L[u],1);
    			int p=lst[u==End[i]?fail[u]:u];
    			int l=dep[u]-dep[p]+1;
    			if(!p || mi<=l) continue; // none or being included
    			mi=l;
    			if(!B[p]) A[++C]=p;
    			B[p]++;
    		}
    		rep(j,1,C) {
    			ans+=T.Que(L[A[j]],R[A[j]])==B[A[j]];
    			B[A[j]]=0;
    		}
    		for(int u=End[i];u;u=fa[u]) T.Add(L[u],-1);
    	}
    	printf("%d
    ",ans);
    }
    
  • 相关阅读:
    原生JS实现日历
    重复的事情让机器来做,简化的思想
    Ext3.1的一些使用讨论
    JS使用Crypto实现AES/ECS/zero-padding加密
    一些提升效率的小知识
    一些很有意思的JS现象
    Tiny Linux -- tce-load
    python sqlalchemy mysql 自动映射
    python 反射
    python 动态导包
  • 原文地址:https://www.cnblogs.com/chasedeath/p/14819940.html
Copyright © 2020-2023  润新知