• 题解 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;
    }
    
  • 相关阅读:
    专职DBA-MySQL体系结构与基本管理
    JSON
    MIME类型
    文件上传下载
    response常用的方法
    2020.11.27小记
    HTTP请求状态码
    1561. Maximum Number of Coins You Can Get
    1558. Minimum Numbers of Function Calls to Make Target Array
    1557. Minimum Number of Vertices to Reach All Nodes
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12383459.html
Copyright © 2020-2023  润新知