C/C++教程

【题解】CF700E Cool Slogans

本文主要是介绍【题解】CF700E Cool Slogans,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

CF700E Cool Slogans

\(\text{Solution:}\)

\(dp,\) 思路都是对的 又死细节上了 对 SAM 的理解还是不够……(或者应该说是 \(dp\))

首先考虑一下什么情况我们可以接上一个串。题目给的是出现了两次,那转化到 SAM 上,我们如何用已知信息来判别?

首先证明一个结论,如果 \(t\) 可以接到当前串的后面,那必然存在一种最优方案使 \(s\) 是 \(t\) 的后缀。

若不是,考虑把后面多余的切掉不会劣。

那么就可以直接扔到 parent 树上考虑了。继续考虑在这种情况下我们如何判定。

假定我们已经求出了一个 \(t\) 的 endpos,

直接扔结论:

\[\exist i\in [endpos-len[t]+len[s],endpos-1],i\in endpos_s \]

证明:首先其作为后缀已经出现了一次,剩下的一次需要在 \(endpos\) 之前,最右端也就是 \(endpos-1;\) 而左端至少要到起点加上其长度的位置。

那就可以愉快 \(dp\) 了,设 \(f[i]\) 表示以 parent 树上 \(i\) 节点结尾的最长长度,显然如果符合条件就直接 \(f[i]=f[x]+1.\) 不符合呢?

当时写的时候就默认给删了……实际上这样不对,显然舍去了一些答案呀……

观察一下,在树上一个后缀容易在深度更深的后缀中出现两次。

这也就意味着我们的直接舍去的做法是错的。所以我们还需要记录一下一个点的前驱状态。

初始的时候转移状态默认为自己,如果一个节点其父亲无法与当前点匹配,那就令其前驱状态为其父亲。

转移的时候均默认从前驱状态转移即可。复杂度是 \(O(n\log n),\) 因为需要线段树合并维护 endpos 来支持查询。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
const int TN=1e7+10;
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
int n;
char s[N];
namespace SGT{
	int ls[TN],rs[TN],node;
	void change(int &x,const int &L,const int &R,const int &pos){
		if(!x)x=++node;
		if(L==R)return;
		int mid=(L+R)>>1;
		if(pos<=mid)change(ls[x],L,mid,pos);
		else change(rs[x],mid+1,R,pos);
	}
	int merge(const int &x,const int &y){
		if(!x||!y)return x|y;
		int p=++node;
		ls[p]=merge(ls[x],ls[y]);
		rs[p]=merge(rs[x],rs[y]);
		return p;
	}
	bool query(const int &x,const int &L,const int &R,const int &l,const int &r){
		if(!x)return false;
		if(L>=l&&R<=r)return true;
		int mid=(L+R)>>1;
		bool res=false;
		if(l<=mid)res|=query(ls[x],L,mid,l,r);
		if(mid<r)res|=query(rs[x],mid+1,R,l,r);
		return res;
	}
}
using namespace SGT;
namespace SAM{
	int len[N],pa[N],ch[N][26],last=1,tot=1,col[N],minr[N],f[N],rt[N];
	int siz[N],prestate[N];
	vector<int>G[N];
	queue<int>q;
	void insert(const int &c,const int &cl){
		int p=last;int np=++tot;last=tot;siz[np]=1;
		len[np]=len[p]+1;col[np]=cl;minr[np]=cl;
		for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
		if(!p)pa[np]=1;
		else{
			int q=ch[p][c];
			if(len[q]==len[p]+1)pa[np]=q;
			else{
				int nq=++tot;
				len[nq]=len[p]+1;
				pa[nq]=pa[q];pa[q]=pa[np]=nq;
				memcpy(ch[nq],ch[q],sizeof ch[q]);
				for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
			}
		}
	}
	void dfs(int x){
		if(col[x])change(rt[x],1,n,col[x]);
		for(auto v:G[x]){
			dfs(v);
			rt[x]=merge(rt[x],rt[v]);
			minr[x]=Max(minr[x],minr[v]);
			siz[x]+=siz[v];
		}
	}
	void Build(){
		for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
		dfs(1);
	}
	void Dp(){
		q.push(1);f[1]=0;
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(auto v:G[x]){
				int endpos=minr[v];
//				printf("%d:[%d %d]\n",v,endpos-len[v]+len[x],endpos-1);
				if(query(rt[prestate[x]],1,n,endpos-len[v]+len[prestate[x]],endpos-1))f[v]=Max(f[v],f[prestate[x]]+1);//,cout<<"?\n";
				else {
                    f[v]=Max(f[v],f[x]),prestate[v]=prestate[x];
                }
                q.push(v);
			}
		}
	}
}
using namespace SAM;
int ep[N];
int main(){
// 	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	scanf("%s",s+1);
    memset(minr,-0x3f,sizeof minr);
	for(int i=1;i<=n;++i){
		int v=s[i]-'a';
		ep[i]=tot+1;
		insert(v,i);
	}
    for(int i=1;i<=tot;++i)f[i]=1,prestate[i]=i;
	Build();
//	for(int i=1;i<=n;++i){
//		printf("%d %d %d\n",ep[i],siz[ep[i]],minr[ep[i]]);
//	}
//	for(int i=1;i<=tot;++i)cout<<minr[i]<<" ";
//	cout<<endl;
	Dp();
//	for(int i=1;i<=tot;++i)cout<<f[i]<<" ";
//	puts("");
	int ans=-1;
	for(int i=1;i<=tot;++i)ans=Max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
这篇关于【题解】CF700E Cool Slogans的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!