多校联训字符串专题


CF547E Mike and Friends

\(\text{SAM}\)\(\text{AC}\) 自动机板子题,线段树合并或离线二位数点均可。

点击查看代码
#include
using namespace std;
int n,q;
char s[1000005];
int tr[26][1000005],fa[1000005],ed[1000005],cnt;
inline void insert(int id){
	int now=0,n=strlen(s+1);
	for(int i=1;i<=n;i++){
		int u=s[i]-'a';
		if(!tr[u][now])tr[u][now]=++cnt,fa[cnt]=now;
		now=tr[u][now];
	}ed[id]=now;
}
int ne[1000005];
queue Q;
vector vec[1000005];
inline void build(){
	for(int i=0;i<26;i++)if(tr[i][0])Q.push(tr[i][0]);
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		for(int i=0;i<26;i++){
			if(!tr[i][x])tr[i][x]=tr[i][ne[x]];
			else {
				ne[tr[i][x]]=tr[i][ne[x]];
				Q.push(tr[i][x]);
			}
		}
	}
	for(int i=1;i<=cnt;i++)vec[ne[i]].push_back(i);
}
int dfn[1000005],siz[1000005],tot;
void dfs(int x){
	siz[x]=1;dfn[x]=++tot;
	for(auto u:vec[x]){
		dfs(u);siz[x]+=siz[u];
	}
}
int tree[1000005];
inline void add(int x,int v){
	while(x<=tot){
		tree[x]+=v;
		x+=(x&-x);
	}
}
inline int query(int x){
	int res=0;
	while(x>0){
		res+=tree[x];
		x&=(x-1);
	}return res;
}
struct node{
	int l,r,op,id;
	node(int _x,int _op,int _id){
		l=dfn[_x];r=dfn[_x]+siz[_x]-1;op=_op;id=_id;
	}
};
vector qry[1000005];
int ans[500005];
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		insert(i);
	}build();dfs(0);
	for(int i=1;i<=q;i++){
		int x,l,r;
		scanf("%d%d%d",&l,&r,&x);
		qry[l-1].push_back(node(ed[x],-1,i));
		qry[r].push_back(node(ed[x],1,i));
	}
	for(int i=1;i<=n;i++){
		for(int x=ed[i];x;x=fa[x])add(dfn[x],1);
		for(auto it:qry[i]){
			ans[it.id]+=it.op*(query(it.r)-query(it.l-1));
		}
	}
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);

	return 0;
}


CF504E Misha and LCP on Tree

序列上的 \(\text{LCP}\) 问题可以哈希来做,考虑树剖,讲路径拆成几个区间,一段一段抵消,即可求出 \(\text{LCP}\)

点击查看代码
#include
using namespace std;
int n,m;
char c[300005];
int ver[600005],ne[600005],head[300005],tot;
inline void link(int x,int y){
	ver[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
}
int fa[300005],dep[300005],siz[300005],son[300005];
void dfs1(int x,int fi){
	fa[x]=fi;dep[x]=dep[fi]+1;siz[x]=1;
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fi)continue;
		dfs1(u,x);siz[x]+=siz[u];
		if(siz[u]>siz[son[x]])son[x]=u;
	}
}
int dfn[300005],top[300005],cnt;
void dfs2(int x,int fi){
	dfn[x]=++cnt;top[x]=fi;
	if(son[x])dfs2(son[x],fi);
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fa[x]||u==son[x])continue;
		dfs2(u,u);
	}
}
char s[600005];
const long long md=1e9+7;
long long hsh[600005],base[600005];
inline void init(){
	base[0]=1;
	for(int i=1;i<=2*n;i++)base[i]=base[i-1]*131%md;
	for(int i=1;i<=n;i++)s[dfn[i]]=c[i];
	for(int i=1;i<=n;i++)s[2*n-dfn[i]+1]=c[i];
	for(int i=1;i<=2*n;i++)hsh[i]=(hsh[i-1]*131+s[i])%md;
}
inline long long Get(int l,int r){
	return ((hsh[r]-hsh[l-1]*base[r-l+1]%md)%md+md)%md;
}
vector > lca(int x,int y){
	vector >le,ri;
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]){
			le.push_back(make_pair(2*n-dfn[x]+1,2*n-dfn[top[x]]+1));
			x=fa[top[x]];
		}else {
			ri.push_back(make_pair(dfn[top[y]],dfn[y]));
			y=fa[top[y]];
		}
	}
	if(dep[x]>dep[y])le.push_back(make_pair(2*n-dfn[x]+1,2*n-dfn[y]+1));
	else ri.push_back(make_pair(dfn[x],dfn[y]));
	while(!ri.empty())le.push_back(ri.back()),ri.pop_back();
	return le;
}
inline int solve(int a,int b,int len){
	int l=0,r=len,res=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(Get(a,a+mid)==Get(b,b+mid))l=mid+1,res=mid;
		else r=mid-1;
	}return res+1;
}
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
inline char gc(){
    if(p1==p2){
        p1=buf;
        p2=buf+fread(buf,1,1000000,stdin);
    }
    if(p1==p2)return EOF;
    return *p1++;
}
inline int read(){
    register int res=0;register char c=gc();
    register bool is=0;
    while(c<'0'||c>'9'){
        if(c=='-')is=1;
        c=gc();
    }
    while(c>='0'&&c<='9'){
        res=(res<<1)+(res<<3)+(c^48);
        c=gc();
    }
    return is?-res:res;
}
int main(){
	scanf("%d",&n);
	scanf("%s",c+1);
	for(int i=1;i > le=lca(a,b),ri=lca(c,d);
		vector >::iterator it=ri.begin();
		int ans=0;
		for(auto x:le){
			while(it!=ri.end()){
				int len=min(it->second-it->first,x.second-x.first);
				if(Get(it->first,it->first+len)==Get(x.first,x.first+len))ans+=len+1;
				else {
					ans+=solve(it->first,x.first,len);
					it=ri.end();break;
				}it->first+=len+1;x.first+=len+1;
				if(it->first>it->second)++it;
				if(x.first>x.second)break;
			}
		}printf("%d\n",ans);
	}

	return 0;
}

CF1012D AB-Strings

Gym 103427M String Problem

考虑给定一棵 \(\text{SAM}\) 如何求出它字典序最大的子串,只需从根节点出发贪心走最大的边即可,容易发现这条路径一定是一个后缀。

用一个 \(\text{vector}\) 记录这条路径,建 \(\text{SAM}\) 时动态维护即可。

点击查看代码
#include
using namespace std;
int n;
char s[1000005];
int son[26][2000005],cnt=1,lst=1,l[2000005],fa[2000005];
int vis[2000005];
vector vec;
inline void insert(int c){
	int i=lst,x=++cnt;l[x]=l[i]+1;lst=x;
	while(i&&!son[c][i]){
		if(~vis[i]){
			bool is=1;
			for(int j=c;j<26;j++)if(son[j][i])is=0;
			if(is){
				while(vec.back()!=i){
					vis[vec.back()]=-1;vec.pop_back();
				}
				vis[x]=vec.size();vec.push_back(x);
			}
		}
		son[c][i]=x;i=fa[i];
	}
	if(!i)fa[x]=1;
	else {
		int j=son[c][i];
		if(l[j]==l[i]+1)fa[x]=j;
		else {
			int e=++cnt;l[e]=l[i]+1;
			for(int t=0;t<26;t++)son[t][e]=son[t][j];
			fa[e]=fa[j];fa[x]=fa[j]=e;
			while(i&&son[c][i]==j){
				if(~vis[i]&&vis[i]+1==vis[j])vec[vis[j]]=e,vis[e]=vis[j],vis[j]=-1;
				son[c][i]=e,i=fa[i];
			}
		}
	}
}
int main(){
	scanf("%s",s+1);n=strlen(s+1);
	memset(vis,-1,sizeof(vis));
	vis[1]=0;vec.push_back(1);
	for(int i=1;i<=n;i++){
		insert(s[i]-'a');
		printf("%ld %d\n",i-vec.size()+2,i);
	}

	return 0;
}




Gym 103409J Suffix Automaton

CF914F Substrings in a String

时限比较宽,使用 \(\text{bitset}\) 优化暴力或对 \(\text{y}\) 的长度进行根号分治均可。

点击查看代码
#include
using namespace std;
int q;
bitset<100005> mp[26];
char s[100005],c[100005];
int main(){
	scanf("%s%d",s+1,&q);
	int n=strlen(s+1);
	for(int i=1;i<=n;i++)mp[s[i]-'a'][i]=1;
	while(q--){
		int op,x,y;
		scanf("%d",&op);
		if(op==1){
			scanf("%d%s",&x,c);
			mp[s[x]-'a'][x]=0;mp[c[0]-'a'][x]=1;
			s[x]=c[0];
		}
		else {
			scanf("%d%d%s",&x,&y,c);
			int len=strlen(c);
			bitset<100005> ans;ans.set();
			for(int i=0;i>i);
			printf("%d\n",max(0,(int)(ans>>x).count()-(int)(ans>>(y-len+2)).count()));
		}
	}

	return 0;
}

CF700E Cool Slogans

考虑如果一个小字符串不是它后面的大字符串的后缀,那么把大字符串后面去掉一部分使其变成后缀,答案不会边劣。

因此最优答案一定可以在 \(\text{parent}\) 树的一条链上取到,建出 \(\text{SAM}\) 后在 \(\text{parent}\) 树上 \(dfs\) 更新答案即可。

点击查看代码
#include
using namespace std;
int n;
namespace Seg{
	int tree[12000005],le[12000005],ri[12000005],cnt;
	void insert(int loc,int &i,int l=1,int r=n){
		if(locr)return ;
		i=++cnt;tree[i]++;
		if(l==r)return ;
		int mid=(l+r)>>1;
		insert(loc,le[i],l,mid);insert(loc,ri[i],mid+1,r);
	}
	int merge(int x,int y,int l=1,int r=n){
		if(!x||!y)return x|y;
		int i=++cnt,mid=(l+r)>>1;
		if(l==r){
			tree[i]=1;return i;
		}
		le[i]=merge(le[x],le[y],l,mid);ri[i]=merge(ri[x],ri[y],mid+1,r);
		tree[i]=tree[le[i]]+tree[ri[i]];
		return i;
	}
	int query(int fr,int to,int i,int l=1,int r=n){
		if(fr>r||to=r)return tree[i];
		int mid=(l+r)>>1;
		return query(fr,to,le[i],l,mid)+query(fr,to,ri[i],mid+1,r);
	}
}
namespace SAM{
	int son[26][400005],fa[400005],l[400005],cnt=1,lst=1,rt[400005],pos[400005];
	inline void insert(int c,int id){
		int i=lst,x=++cnt;lst=x;l[x]=l[i]+1;
		while(i&&!son[c][i])son[c][i]=x,i=fa[i];
		if(!i)fa[x]=1;
		else {
			int j=son[c][i];
			if(l[j]==l[i]+1)fa[x]=j;
			else {
				int e=++cnt;l[e]=l[i]+1;
				for(int t=0;t<26;t++)son[t][e]=son[t][j];
				fa[e]=fa[j];fa[x]=fa[j]=e;
				while(i&&son[c][i]==j)son[c][i]=e,i=fa[i];
			}
		}Seg::insert(id,rt[x]);pos[x]=id;
	}
	inline bool cmp(int x,int y){
		return l[x]>l[y];
	}
	vector vec[400005];
	inline void build(){
		vector tmp;
		for(int i=1;i<=cnt;i++)tmp.push_back(i);
		sort(tmp.begin(),tmp.end(),cmp);
		for(auto it:tmp){
			if(fa[it]){
				pos[fa[it]]=max(pos[fa[it]],pos[it]);
				rt[fa[it]]=Seg::merge(rt[fa[it]],rt[it]);
				vec[fa[it]].push_back(it);
			}
		}
	}
	int dep[400005],ans=1;
	void dfs(int x,int fi){
		if(x!=fi&&Seg::query(pos[x]-l[x]+l[fi],pos[x]-1,rt[fi]))dep[x]=dep[fi]+1,fi=x;
		else dep[x]=dep[fi];ans=max(ans,dep[x]);
		for(auto u:vec[x]){
			dfs(u,fi);
		}
	}
}
char s[200005];
int main(){
	scanf("%d",&n);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)SAM::insert(s[i]-'a',i);
	SAM::build();
	for(auto u:SAM::vec[1])SAM::dep[u]=1,SAM::dfs(u,u);
	printf("%d\n",SAM::ans);
 
	return 0;
}

CF906E Reverses

发现如果对于两个字符串,一个可以通过反转得到另一个,那么把两个字符串一个字符一个字符交叉放置将会得到一个回文串。

\(s\)\(t\) 一个字符一个字符交替放置,问题转化为将的字符串划分成若干个偶回文串,使得长度大于 \(2\) 的偶回文串尽量少,建出 \(\text{PAM}\) 并在上面 \(dp\) 即可。

点击查看代码
#include
using namespace std;
int n;
namespace PAM{
	int son[26][1000005],fch[26][1000005],l[1000005],fa[1000005],cnt,len,now,dis[1000005],slink[1000005];
	int s[1000005];
	inline void init(){l[0]=-1;s[0]=-1;cnt=1;}
	inline void insert(int c){
		int p=(s[len-l[now]]==c?now:fch[c][now]);
		s[++len]=c;
		if(son[c][p])now=son[c][p];
		else {
			now=son[c][p]=++cnt;
			int fi=fa[now]=(p?son[c][fch[c][p]]:1);
			l[now]=l[p]+2;dis[now]=l[now]-l[fi];
			slink[now]=(dis[now]==dis[fi]?slink[fi]:fi);
			for(int i=0;i<26;i++)fch[i][now]=(s[len-l[fi]]==i?fi:fch[i][fi]);
		}
	}
	int dp[1000005],tmp[1000005],loc[1000005];
	inline int cmp(int x,int y){
		return dp[x]1;j=slink[j]){
				int blt=l[slink[j]]+dis[j];
				tmp[j]=len-blt;
				if(fa[j]!=slink[j])tmp[j]=cmp(tmp[j],tmp[fa[j]]);
			}
			dp[len]=1e9;
			insert(b[i]-'a');
			if(a[i]==b[i])dp[len]=dp[len-2],loc[len]=len-2;
			for(int j=now;j>1;j=slink[j]){
				int blt=l[slink[j]]+dis[j];
				tmp[j]=len-blt;
				if(fa[j]!=slink[j])tmp[j]=cmp(tmp[j],tmp[fa[j]]);
				if(dp[len]>dp[tmp[j]]+1){
					dp[len]=dp[tmp[j]]+1;
					loc[len]=tmp[j];
				}
			}
		}
		if(dp[len]>=1e9)puts("-1");
		else {
			printf("%d\n",dp[len]);
			vector vec;
			for(int i=len;i;i=loc[i])vec.push_back(i/2),vec.push_back(loc[i]/2+1);
			reverse(vec.begin(),vec.end());
			for(int i=0;i

CF666E Forensic Examination

\(\text{SAM}\) 上倍增找对应节点,线段树合并维护子树内区间最大值。

点击查看代码
#include
using namespace std;
int n,q;
namespace Seg{
	pair tree[15000005];
	int le[15000005],ri[15000005],cnt;
	void insert(int loc,int &i,int l=1,int r=n){
		if(locr)return ;
		if(!i)i=++cnt;
		if(l==r){
			tree[i].first++;tree[i].second=-loc;
			return ;
		}
		int mid=(l+r)>>1;
		insert(loc,le[i],l,mid);insert(loc,ri[i],mid+1,r);
		tree[i]=max(tree[le[i]],tree[ri[i]]);
	}
	int merge(int x,int y,int l=1,int r=n){
		if(!x||!y)return x|y;
		int i=++cnt;
		if(l==r)tree[i]=make_pair(tree[x].first+tree[y].first,-l);
		else {
			int mid=(l+r)>>1;
			le[i]=merge(le[x],le[y],l,mid);
			ri[i]=merge(ri[x],ri[y],mid+1,r);
			tree[i]=max(tree[le[i]],tree[ri[i]]);
		}return i;
	}
	pair query(int fr,int to,int i,int l=1,int r=n){
		if(!i||fr>r||to=r)return tree[i];
		int mid=(l+r)>>1;
		return max(query(fr,to,le[i],l,mid),query(fr,to,ri[i],mid+1,r));
	}
}
namespace SAM{
	namespace Trie{
		int tr[26][1000005],cnt=1;
		vector vec[1000005];
		inline void insert(char* s,int id){
			int m=strlen(s+1),now=1;
			for(int i=1;i<=m;i++){
				if(!tr[s[i]-'a'][now])tr[s[i]-'a'][now]=++cnt;
				now=tr[s[i]-'a'][now];if(id)vec[now].push_back(id);
			}
		}
	}
	int son[26][2000005],fa[2000005],l[2000005],cnt=1;
	inline int insert(int c,int lst){
		int i=lst,x=++cnt;l[x]=l[i]+1;
		while(i&&!son[c][i])son[c][i]=x,i=fa[i];
		if(!i)fa[x]=1;
		else {
			int j=son[c][i];
			if(l[j]==l[i]+1)fa[x]=j;
			else {
				int e=++cnt;
				for(int t=0;t<26;t++)son[t][e]=son[t][j];
				l[e]=l[i]+1;fa[e]=fa[j];fa[x]=fa[j]=e;
				while(i&&son[c][i]==j)son[c][i]=e,i=fa[i];
			}
		}return x;
	}
	queue q;
	int mp[2000005],rt[2000005],bac[2000005],a[2000005];
	inline void build(){
		q.push(1);mp[1]=1;
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=0;i<26;i++){
				int u=Trie::tr[i][x];
				if(u){
					mp[u]=insert(i,mp[x]);q.push(u);
					for(auto it:Trie::vec[u])Seg::insert(it,rt[mp[u]]);
				}
			}
		}
		for(int i=1;i<=cnt;i++)bac[l[i]]++;
		for(int i=1;i<=cnt;i++)bac[i]+=bac[i-1];
		for(int i=1;i<=cnt;i++)a[bac[l[i]]--]=i;
		for(int i=cnt;i;i--){
			int x=a[i];
			rt[fa[x]]=Seg::merge(rt[fa[x]],rt[x]);
		}
	}
}
int mp[500005],fa[21][2000005];
inline void query(int x,int lim,int l,int r){
	for(int i=20;~i;i--)if(SAM::l[fa[i][x]]>=lim)x=fa[i][x];
	pair it=Seg::query(l,r,SAM::rt[x]);
	printf("%d %d\n",-it.second,it.first);
}
char s[500005];
int main(){
	scanf("%s",s+1);int m=strlen(s+1);
	SAM::Trie::insert(s,0);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		SAM::Trie::insert(s,i);
	}
	SAM::build();
	for(int i=1;i<=m;i++)mp[i]=SAM::mp[i+1];
	for(int i=1;i<=SAM::cnt;i++)fa[0][i]=SAM::fa[i];
	for(int i=1;i<21;i++){
		for(int j=1;j<=SAM::cnt;j++)fa[i][j]=fa[i-1][fa[i-1][j]];
	}
	scanf("%d",&q);
	while(q--){
		int ql,qr,l,r;
		scanf("%d%d%d%d",&l,&r,&ql,&qr);
		query(mp[qr],qr-ql+1,l,r);
	}

	return 0;
}


【UR #20】机器蚤分组

考虑暴力怎么做,可以建出 \(\text{SAM}\) 和后缀树,把所有边放到一张图中跑最小链覆盖,这样做显然是不行的考虑优化。

考虑 \(\text{Dilworth}\) 引理,最小链覆盖等于最大独立集,考虑找出最大的 \(k\) 使得存在 \(k\) 个子串两两不同,这个 \(k\) 就是答案。

容易发现对于 \(k\) 个两两不同的子串,我们一定可以通过调整使得它们长度相同。

对于一个长度为 \(n\) 的字符串,它有 \(n-k+1\) 个长度为 \(k\) 的子区间,如果有两个子区间相同,即使将 \(k\) 增大,由于子区间数减少了,答案不会变的更优,如果将 \(k\) 增大,答案不会变的更劣。

因此我们只需找出最小的 \(k\) 使得区间内不存在两个长度为 \(k\) 的相等的子串,等价于区间内的所有长度大于 \(k\) 的前缀的最长公共后缀长度不超过 \(k\)

建出 \(\text{parent}\) 树后 \(\text{LCT}\) + 扫描线维护即可。

点击查看代码
#include
using namespace std;
int n,q;
char s[100005];
namespace Seg{
	int tree[100005];
	inline void add(int x,int v){
		while(x){
			tree[x]=max(v,tree[x]);
			x&=(x-1);
		}
	}
	inline int query(int x){
		int res=0;
		while(x<=n){
			res=max(res,tree[x]);
			x+=(x&-x);
		}
		return res;
	}
}
namespace SAM{
	int son[26][200005],fa[200005],l[200005],cnt=1,lst=1;
	inline void insert(int c){
		int i=lst,x=++cnt;l[x]=l[i]+1;lst=x;
		while(i&&!son[c][i])son[c][i]=x,i=fa[i];
		if(!i)fa[x]=1;
		else {
			int j=son[c][i];
			if(l[j]==l[i]+1)fa[x]=j;
			else {
				int e=++cnt;l[e]=l[i]+1;
				for(int t=0;t<26;t++)son[t][e]=son[t][j];
				fa[e]=fa[j];fa[x]=fa[j]=e;
				while(i&&son[c][i]==j)son[c][i]=e,i=fa[i];
			}
		}
	}
}
namespace LCT{
	int son[2][200005],fa[200005],val[200005],lazy[200005];
	inline bool isroot(int x){
		return son[0][fa[x]]!=x&&son[1][fa[x]]!=x;
	}
	inline void push(int x){
		if(lazy[x]){
			val[son[0][x]]=lazy[son[0][x]]=lazy[x];
			val[son[1][x]]=lazy[son[1][x]]=lazy[x];
		}lazy[x]=0;
	}
	inline void rotate(int x){
		int y=fa[x],z=fa[y];
		if(!isroot(y))son[son[1][z]==y][z]=x;
		bool is=(son[1][y]==x);
		son[is][y]=son[!is][x];fa[son[is][y]]=y;
		son[!is][x]=y;fa[y]=x;fa[x]=z;
	}
	int stk[200005],top;
	inline void splay(int x){
		stk[++top]=x;
		for(int i=x;!isroot(i);i=fa[i])stk[++top]=fa[i];
		while(top)push(stk[top--]);
		while(!isroot(x)){
			int y=fa[x],z=fa[y];
			if(!isroot(y)){
				if((son[1][z]==y)^(son[1][y]==x))rotate(x);
				else rotate(y);
			}rotate(x);
		}
	}
	inline void access(int x,int id){
		for(int i=0;x;i=x,x=fa[x]){
			splay(x);son[1][x]=i;Seg::add(val[x],SAM::l[x]);
			val[x]=lazy[x]=id;
		}
	}
}
int ans[100005],ed[100005];
vector > vec[100005];
int main(){
	scanf("%d%d",&n,&q);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)SAM::insert(s[i]-'a'),ed[i]=SAM::lst;
	for(int i=1;i<=SAM::cnt;i++)LCT::fa[i]=SAM::fa[i];
	for(int i=1;i<=q;i++){
		int l,r;scanf("%d%d",&l,&r);ans[i]=r-l+1;
		vec[r].push_back(make_pair(l,i));
	}
	for(int i=1;i<=n;i++){
		LCT::access(ed[i],i);
		for(auto it:vec[i]){
			int L=1,R=ans[it.second],res=0;
			while(L<=R){
				int mid=(L+R)>>1;
				if(Seg::query(it.first+mid-1)>=mid)res=mid,L=mid+1;
				else R=mid-1;
			}
			ans[it.second]-=res;
		}
	}
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);

	return 0;
}

[AGC007F] Shik and Copying String

首先,这个过程可以用折线表示:

可以发现,每条折线都尽量靠右是最优的,一旦画不下了,就加一行。

现在问题变成了如何高效地维护这一贪心。

\(S_0=T\) 时,先特判掉,输出 \(0\)

由于每次拐点都会往左下移动一格,我们可以用队列来维护当前折线的每个拐点(折线往右拐的点,也就是 \(S_i[j]=S_i[j-1]\)\(j-1\) 这个点)(不包括最后一行的拐点),其中靠近队首表示靠下(离 \(T\) 较近)的拐点,靠近队尾表示靠上(靠近 \(S_0\))的拐点。

点击查看代码
#include
using namespace std;
int n,ans;
char s[1000005],t[1000005];
queue q;
int main(){
	scanf("%d",&n);
	scanf("%s%s",s+1,t+1);
	bool is=1;
	for(int i=1;i<=n;i++)if(s[i]!=t[i])is=0;
	if(is){
		puts("0");
		return 0;
	}
	int down=n,up=n;
	while(down){
		while(down!=1&&t[down-1]==t[down])down--;
		while(up&&(up>down||s[up]!=t[down]))up--;
		if(!up){
			puts("-1");
			return 0;
		}
		while(!q.empty()&&q.front()-q.size()>=down)q.pop();
		if(up!=down)q.push(up);--down;
		ans=max(ans,(int)q.size()+1);
	}
	printf("%d\n",ans);

	return 0;
}


Gym 103371B Cilantro

Gym 103409H Popcount Words

【ULR #1】打击复读

为了支持修改,我们要对于每个后缀预处理出它所有前缀的右权值乘上前缀的出现次数之和。

在对整个串建出 \(\text{SAM}\) 后,可以很容易地统计出一个字符串的右权值以及它在整个串中的出现次数。

一个暴力的思路是我们可以将每个后缀放进 \(\text{SAM}\) 里跑,将经过路径上所有点的权值加起来就是这个后缀的权值。

然而这样做是 \(O(n^2)\) 的,考虑优化这一过程,可以发现这个过程实际上是在遍历一棵插入了所有后缀的 \(\text{Trie}\) ,这样的 \(Trie\) 是可以压缩成一棵点数为 \(O(n)\) 级别的后缀树的。

容易发现,对于后缀树上的一条边,在 \(\text{SAM}\) 对应的路径一定是一条链,即路径上的所有点出度都是 \(1\) ,我们把这些节点缩起来就可以了,即所谓 “压缩后缀自动机”

点击查看代码
#include
using namespace std;
int n,m;
struct SAM{
	int son[4][1000005],fa[1000005],l[1000005],cnt,lst,siz[1000005],endpos[1000005];
	unsigned long long val[1000005];
	SAM(){cnt=lst=1;}
	inline void insert(int c,unsigned long long v){
		int i=lst,x=++cnt;l[x]=l[i]+1;lst=x;endpos[x]=l[x];
		while(i&&!son[c][i])son[c][i]=x,i=fa[i];
		if(!i)fa[x]=1;
		else {
			int j=son[c][i];
			if(l[j]==l[i]+1)fa[x]=j;
			else {
				int e=++cnt;l[e]=l[i]+1;
				for(int t=0;t<4;t++)son[t][e]=son[t][j];
				fa[e]=fa[j];fa[j]=fa[x]=e;endpos[e]=endpos[j];
				while(i&&son[c][i]==j)son[c][i]=e,i=fa[i];
			}
		}
		val[x]=v;siz[x]=1;
	}
	int a[1000005],bac[1000005];
	int mp[1000005];
	bool vis[1000005];
	inline void build(){
		for(int i=1;i<=cnt;i++)bac[l[i]]++;
		for(int i=1;i<=n;i++)bac[i]+=bac[i-1];
		for(int i=1;i<=cnt;i++)a[bac[l[i]]--]=i;
		for(int i=lst;i;i=fa[i])vis[i]=1;
		for(int i=cnt;i;i--){
			int x=a[i];
			if(fa[x]){
				val[fa[x]]+=val[x];siz[fa[x]]+=siz[x];
			}
			val[x]*=siz[x];
			int loc=0;
			for(int j=0;j<4;j++)if(son[j][x]){
				if(!loc)loc=son[j][x];
				else loc=-1;
			}
			if(loc>0&&!vis[x])val[x]+=val[loc],mp[x]=mp[loc];
			else mp[x]=x;
		}
	}
}pre,suf;
char s[500005];
unsigned long long wl[500005],wr[500005],sum[1000005],tmp[500005];
int ed[500005];
vector vec[1000005];
void dfs(int x,int y){
	for(auto u:vec[x]){
		int op=s[n-suf.endpos[u]+suf.l[x]+1];
		sum[u]=sum[x]+pre.val[pre.son[op][y]];
		dfs(u,pre.mp[pre.son[op][y]]);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++){
		if(s[i]=='A')s[i]=0;
		else if(s[i]=='T')s[i]=1;
		else if(s[i]=='C')s[i]=2;
		else if(s[i]=='G')s[i]=3;
	}
	for(int i=1;i<=n;i++)scanf("%llu",&wl[i]);
	for(int i=1;i<=n;i++)scanf("%llu",&wr[i]);
	for(int i=1;i<=n;i++)pre.insert(s[i],wr[i]);
	for(int i=n;i>=1;i--)suf.insert(s[i],0),ed[i]=suf.lst;
	pre.build();
	for(int i=1;i<=suf.cnt;i++)vec[suf.fa[i]].push_back(i);
	dfs(1,1);
	for(int i=1;i<=n;i++)tmp[i]=sum[ed[i]];
	unsigned long long ans=0;
	for(int i=1;i<=n;i++)ans+=wl[i]*tmp[i];
	printf("%llu\n",ans);
	while(m--){
		int x;unsigned long long v;
		scanf("%d%llu",&x,&v);
		ans+=(v-wl[x])*tmp[x];wl[x]=v;
		printf("%llu\n",ans);
	}


	return 0;
}