zoukankan      html  css  js  c++  java
  • 题解 CF700E Cool Slogans

    建出SAM及parent树。根据大家所熟知的套路,我们可以用线段树合并来维护enspos集合,这样就可以(O(log n))查询线段树某个节点上的子串在原串的某个区间([l,r])内出现了几次。具体做法请见 NOI2018你的名字。

    引理:

    对于parent树上任意一个节点(x),以及它的子树内任意一个节点(y),设节点(y)代表的最长串为(S),设节点(x)代表的串为(T_1,T_2,T_3,dots,T_k),设(F(S,T))表示串(T)(S)中的出现次数,则

    [F(S,T_1) = F(S,T_2) = F(S,T_3) =dots=F(S,T_k) ]

    证明:

    考虑如果存在一个(|T_a|+1=|T_b|),且(T_a)(T_b)(S)内的出现次数不同。

    此时要么是存在一个能匹配(T_a)的位置,它的左边不匹配(T_b),那就说明(T_a)在原串里的出现次数比(T_b)多,与“(T_a,T_b)在SAM的同一个节点上”矛盾。

    要么是在(T_a)(S)的一个前缀,而(T_b)因为太长被卡了一下(如下图)。

    如果要使(T_a,T_b)出现次数一样,那么虽然(S)的前面到此为止了((T_b)被卡了一下),但真正的原串里在这个位置(T_b)肯定没被卡。所以(S)必定存在一个儿子,我们称这个串为(S')。它满足:(|S'|=|S|+1),且(T_a)在这个(S')中出现次数和(T_b)一样(即(T_b)没有被卡)。

    又因为(S')(S)不在同一个节点上(因为(S)是它所在节点的最长串),所以(S')在原串里的出现次数必定少于(S)。当(S')出现时,(T_a,T_b)得到的出现次数相同;而在某个(或某些)(S)出现了,(S')却没有出现的位置,(T_a)就会比(T_b)多匹配(1)次。这还是与“(T_a,T_b)在SAM的同一个节点上”矛盾。

    故原命题得证。

    有了这个结论,我们就可考虑在parent树上DP。设(dp[u])表示以(u)这个节点所代表的的最长串,作为挑选出来的答案集合中最后一个串时,答案集合中最多能有多少串。

    (dp[u])肯定是从(u)的一个子串转移。考虑如果从(u)的后缀转移,答案肯定不会变劣,因为这个转移点“子串”在(u)中最后一次出现的位置之后的部分,是无用的,把这部分砍掉答案不会变劣。甚至还可以发现,答案集合中每个串一定是下一个串的border。于是我们直接从parent树上的祖先转移到(dp[u])即可。对每个(u),在它的祖先中二分出一个深度最大的,且在(u)中出现了至少(2)次的节点(v),直接令(dp[u]=dp[v]+1)即可。

    答案就是(max_{i=1}^{n}{dp[i]})

    时间复杂度(O(nlog n))

    参考代码:

    //problem:CF700E
    #include <bits/stdc++.h>
    using namespace std;
     
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fst first
    #define scd second
     
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
     
    namespace Fread{
    const int MAXN=1<<20;
    char buf[MAXN],*S,*T;
    inline char getchar(){
    	if(S==T){
    		T=(S=buf)+fread(buf,1,MAXN,stdin);
    		if(S==T)return EOF;
    	}
    	return *S++;
    }
    }//namespace Fread
    #ifdef ONLINE_JUDGE
    	#define getchar Fread::getchar
    #endif
    inline int read(){
    	int f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    inline ll readll(){
    	ll f=1,x=0;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    /*  ------  by:duyi  ------  */ // dysyn1314
    const int MAXN=200000;
    char s[MAXN+5];
    struct SegmentTree{
    	int sum[MAXN*40],ls[MAXN*40],rs[MAXN*40],cnt;
    	void ins(int &p,int l,int r,int pos){
    		if(!p)p=++cnt;sum[p]++;
    		if(l==r)return;
    		int mid=(l+r)>>1;
    		if(pos<=mid)ins(ls[p],l,mid,pos);
    		else ins(rs[p],mid+1,r,pos);
    	}
    	int merge(int x,int y){
    		if(!x||!y) return x+y;
    		int z=++cnt;
    		sum[z]=sum[x]+sum[y];
    		ls[z]=merge(ls[x],ls[y]);
    		rs[z]=merge(rs[x],rs[y]);
    		return z;
    	}
    	int query(int p,int tl,int tr,int ql,int qr){
    		if(!p) return 0;
    		if(ql<=tl && qr>=tr) return sum[p];
    		int mid=(tl+tr)>>1,res=0;
    		if(ql<=mid)res+=query(ls[p],tl,mid,ql,qr);
    		if(qr>mid)res+=query(rs[p],mid+1,tr,ql,qr);
    		return res;
    	}
    	SegmentTree(){}
    }T;
     
    int n,cnt,ed,fa[MAXN*2+5],mp[MAXN*2+5][26],len[MAXN*2+5],pos[MAXN*2+5],rt[MAXN*2+5];
    void ins(int c){
    	int p=ed;ed=++cnt;len[ed]=len[p]+1;pos[ed]=len[ed];T.ins(rt[ed],1,n,pos[ed]);
    	for(;p&&!mp[p][c];p=fa[p])mp[p][c]=ed;
    	if(!p){fa[ed]=1;return;}
    	int q=mp[p][c];
    	if(len[q]==len[p]+1){fa[ed]=q;return;}
    	len[++cnt]=len[p]+1;pos[cnt]=pos[ed];
    	for(int i=0;i<26;++i)mp[cnt][i]=mp[q][i];
    	fa[cnt]=fa[q];fa[q]=fa[ed]=cnt;
    	for(;mp[p][c]==q;p=fa[p])mp[p][c]=cnt;
    }
    struct EDGE{int nxt,to;}edge[MAXN*2+5];
    int head[MAXN*2+5],tot;
    inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
    void dfs1(int u){
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		dfs1(v);
    		rt[u]=T.merge(rt[u],rt[v]);
    	}
    }
    int sta[MAXN*2+5],top,dp[MAXN*2+5],ans;
    int ask(int small,int big){
    	//small在big里出现了多少次
    	int l=pos[big]-len[big]+1,r=pos[big];
    	if(r-l+1<len[small])return 0;
    	return T.query(rt[small],1,n,l+len[small]-1,r);
    }
    void dfs2(int u){
    	if(u!=1)dp[u]=1;
    	if(top){
    //		for(int i=top;i>=1;--i){
    //			if(i<top)assert(dp[sta[i]]<=dp[sta[i+1]]);
    //			if(ask(sta[i],u)>=2){
    //				dp[u]=max(dp[u],dp[sta[i]]+1);
    //			}
    //		}
    		int l=1,r=top;
    		while(l<r){
    			int mid=(l+r+1)>>1;
    			if(ask(sta[mid],u)>=2)l=mid;
    			else r=mid-1;
    		}
    		if(ask(sta[l],u)>=2)dp[u]=dp[sta[l]]+1;
    	}
    	ans=max(ans,dp[u]);
    	if(u!=1)sta[++top]=u;
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		dfs2(v);
    	}
    	if(u!=1)--top;
    }
    int main() {
    	cnt=ed=1;
    	scanf("%d%s",&n,s+1);
    	for(int i=1;i<=n;++i)ins(s[i]-'a');
    	for(int i=2;i<=cnt;++i)add_edge(fa[i],i);
    	dfs1(1);dfs2(1);cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    React教程(一) React介绍与搭建
    微信公众号订阅号以及服务号通过网页授权获取用户openid方法
    AES,DES加密JS源文件及其使用方法
    R 分类进行数值处理
    C++ const 关键字总结
    软工lintcode作业
    [恶意软件分析]DroidBox的环境搭建与使用
    Android第三次作业
    Android第二次作业
    android 第一次作业
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12383459.html
Copyright © 2011-2022 走看看