Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划

原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html

题解

首先建个SAM。

一个结论:对于parent树上任意一个点x,以及它所代表的子树内任意一个点y,设节点y代表的最长串为S,设节点x代表的串为T1,T2,T3,...,设 F(S,T) 表示串T在S中的出现次数,则 F(S,T1) = F(S,T2) = F(S,T3) = ...

证明:假设串 Ta 和 Tb 在 S 中的出现次数不同,且 |Ta|+1=|Tb| 则必然存在一个位置,使得将 Tb 放在这里的时候,它的最左端点不和 S 匹配,其他位置都匹配,这样的话,Tb 的 Right 集合至少比 Ta 多这个位置,与 “Ta,Tb 都是节点 x 代表的串” 矛盾。故原命题得证。

 

第二个结论:一定存在一个最优解,使得 $\forall 1<i\leq k$, $S_{i-1}$ 是 $S_i$ 的 Border 。

这个很好证,如果不是 Border ,把 $S_i$ 两边多出来的割掉一定不亏。

 

于是我们可以开始规划算法了。

设 $dp[S]$ 表示每次保证前一个串在后一个串中出现至少 2 次,从空串转移到串 $S$ 的最多转移次数。

我们把状态用 parent 树上的节点表示,由于第一个结论,对于每一个节点,我们可以只把这个节点代表的最长串作为有效状态;转移的时候,只要看看父亲的串在当前节点的串中出现次数是否至少2次,如果不到,就直接继承父亲的结果,否则更新为当前结果; 判断出现多少次需要处理出 Right 集合,线段树合并即可。

代码

#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
using namespace std;
typedef long long LL;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f|=ch=='-',ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=200005*2;
int n;
namespace seg{
	const int S=N*50;
	int ls[S],rs[S],size[S],cnt,root;
	void Init(){
		cnt=root=0;
		clr(ls),clr(rs),clr(size);
	}
	void Ins(int &rt,int L,int R,int x){
		if (!rt)
			rt=++cnt;
		size[rt]++;
		if (L==R)
			return;
		int mid=(L+R)>>1;
		if (x<=mid)
			Ins(ls[rt],L,mid,x);
		else
			Ins(rs[rt],mid+1,R,x);
	}
	int Merge(int a,int b,int L,int R){
		if (!a||!b)
			return a+b;
		int rt=++cnt;
		if (L==R)
			size[rt]=1;
		else {
			int mid=(L+R)>>1;
			ls[rt]=Merge(ls[a],ls[b],L,mid);
			rs[rt]=Merge(rs[a],rs[b],mid+1,R);
			size[rt]=size[ls[rt]]+size[rs[rt]];
		}
		return rt;
	}
	int Query(int rt,int L,int R,int xL,int xR){
		if (!rt||xL>R||L>xR)
			return 0;
		if (xL<=L&&R<=xR)
			return size[rt];
		int mid=(L+R)>>1;
		return Query(ls[rt],L,mid,xL,xR)
			  +Query(rs[rt],mid+1,R,xL,xR);
	}
}
namespace SAM{
	int last,size,root;
	struct Node{
		int Next[26],fa,Max,pos;
	}t[N];
	int Init(){
		clr(t);
		return last=size=root=1;
	}
	void extend(int c,int ps){
		int p=last,np=++size,q,nq;
		t[np].Max=t[p].Max+1,t[np].pos=ps;
		for (;p&&!t[p].Next[c];p=t[p].fa)
			t[p].Next[c]=np;
		if (!p)
			t[np].fa=root;
		else {
			q=t[p].Next[c];
			if (t[p].Max+1==t[q].Max)
				t[np].fa=q;
			else {
				nq=++size;
				t[nq]=t[q],t[nq].Max=t[p].Max+1,t[nq].pos=ps;
				t[np].fa=t[q].fa=nq;
				for (;p&&t[p].Next[c]==q;p=t[p].fa)
					t[p].Next[c]=nq;
			}
		}
		last=np;
	}
	int id[N],tax[N],rt[N];
	void sort(){
		clr(tax);
		for (int i=1;i<=size;i++)
			tax[t[i].Max]++;
		for (int i=1;i<=size;i++)
			tax[i]+=tax[i-1];
		for (int i=1;i<=size;i++)
			id[tax[t[i].Max]--]=i;
	}
	void build(){
		sort();
		seg::Init();
		for (int i=size;i>1;i--)
			seg::Ins(rt[id[i]],1,n,t[id[i]].pos);
		for (int i=size;i>1;i--){
			int x=id[i],f=t[x].fa;
			rt[f]=seg::Merge(rt[f],rt[x],1,n);
		}
	}
	int dp[N],nid[N];
	int Horse_NMDP(){
		int ans=0;
		dp[1]=0,nid[1]=1;
		for (int i=2;i<=size;i++){
			int x=id[i],f=nid[t[x].fa];
			if (f==1||seg::Query(rt[f],1,n,t[x].pos-t[x].Max+t[f].Max
										  ,t[x].pos)>=2)
				dp[x]=dp[f]+1,nid[x]=x;
			else
				dp[x]=dp[f],nid[x]=f;
			ans=max(ans,dp[x]);
		}
		return ans;
	}
}
using SAM::t;
using SAM::extend;
char s[N];
int main(){
	n=read();
	scanf("%s",s+1);
	SAM::Init();
	for (int i=1;i<=n;i++)
		extend(s[i]-'a',i);
	SAM::build();
	cout<<SAM::Horse_NMDP()<<endl;
	return 0;
}

  

posted @ 2019-02-27 19:18  zzd233  阅读(226)  评论(0编辑  收藏  举报