字符串作业(上)

「雅礼集训 2017 Day7」事情的相似度

求区间 [ l , r ] [l,r] [l,r]内两个前缀 s [ 1.. a ] , s [ 1... b ] ( a , b ∈ [ l , r ] , a ≠ b ) s[1..a],s[1...b](a,b\in [l,r],a\neq b) s[1..a],s[1...b](a,b[l,r],a=b)的最长公共后缀长度的最大值。

后缀自动机,用 LCT \texttt{LCT} LCT维护 fail \texttt{fail} fail树。
离线,从前到后每次加入一个前缀,将这个前缀在后缀自动机上的点 a c c e s s access access
发现这是一个每次将树上一个点到根的路径染色的形式。
a c c e s s access access时维护每个点被最后一次染色的时间。
当新的一次 a c c e s s access access抵达一个点 x x x时,这个 x x x的最后一次染色和这次染色可以看做这两个前缀的 l c a lca lca到根的路径包含 x x x
直接对于最后一次染色的时间在树状数组上对 x x x的字符串长度取 max ⁡ \max max
处理到前缀 r r r时在树状数组上回答所有 [ l , r ] [l,r] [l,r]的询问即可。

A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m;
namespace BIT{
   
	int tr[maxn];
	void upd(int u,int v){
    for(;u;u-=u&-u) tr[u]=max(tr[u],v); }
	int qry(int u){
    int r=0;for(;u<=n;u+=u&-u) r=max(r,tr[u]);return r; }
}
char s[maxn];
int fa[maxn],ch[maxn][2],tr[maxn][2],tg[maxn],pos[maxn],len[maxn],tot=1,last=1;
void ins(int c){
    // rooted 1 
	int u = ++tot , p = last , q;
	len[last = u] = len[p] + 1;
	for(;p && !tr[p][c];p=fa[p]) tr[p][c]=u;
	if(!p) fa[u] = 1;
	else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
	else{
   
		int v = ++tot;
		tr[v][0] = tr[q][0],
		tr[v][1] = tr[q][1],
		len[v] = len[p] + 1,
		fa[v] = fa[q];
		for(;p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[u] = fa[q] = v;
	}
}
#define pa fa[x]
int inr(int x){
    return ch[pa][1] == x; }
int isr(int x){
    return ch[pa][0] ^ x && ch[pa][1] ^ x; }
void dt(int x){
    
	if(ch[x][0]) tg[ch[x][0]] = tg[x];
	if(ch[x][1]) tg[ch[x][1]] = tg[x];
}
void dtpath(int x){
    if(!isr(x)) dtpath(pa);dt(x); }
void rot(int x){
    
	int y = fa[x] , z = fa[y] , c = inr(x);
	if(!isr(y)) ch[z][inr(y)] = x;
	(ch[y][c] = ch[x][!c]) && (fa[ch[y][c]] = y);
	fa[fa[ch[x][!c] = y] = x] = z;
}
void splay(int x){
   
	for(dtpath(x);!isr(x);rot(x))
		if(!isr(pa))
			rot(inr(pa) ^ inr(x) ? x : pa);
}
int access(int x,int id){
   
	int y = 0;
	for(;x;x=fa[y=x]){
   
		splay(x);
		if(tg[x])
			BIT::upd(tg[x] , len[x]);
		ch[x][1] = y;
		tg[x] = id;
	}
	return y;
}
vector<int>G[maxn];
int l[maxn],r[maxn],ans[maxn];

int main(){
   
	scanf("%d%d",&n,&m);
	scanf("%s",s+1);
	rep(i,1,n) ins(s[i]-'0'),pos[i] = last;
	rep(i,1,m) scanf("%d%d",&l[i],&r[i]),G[r[i]].push_back(i);
	rep(i,1,n){
   
		access(pos[i],i);
		rep(j,0,G[i].size()-1){
   
			int v = G[i][j];
			ans[v] = BIT::qry(l[v]);
		}
	}
	rep(i,1,m) printf("%d\n",ans[i]);
}

「十二省联考 2019」字符串问题

给定一个字符串,给出 n a n_a na个点,每个点是字符串中的一个子串 S [ l . . . r ] S[l...r] S[l...r],每个点有若干类边,每类边可以被表示为该点连向 包含字符串某一个子串 S [ a . . . b ] S[a...b] S[a...b]作为前缀 的点,求最长路或判断没有最长路。

发现作为前缀这个条件,可以简单的对反串建后缀自动机,则连上 f a i l \rm fail fail树中父亲到儿子的有向边即可表示作为前缀这个条件。
但是有一个细节上的问题就是后缀自动机的点是多个不同的串,在后缀自动机上是同一个点的串有时需要连边有时又不需要连边,这个问题很好解决,就对于每个点存一下所有串,按长度排个序再连边即可。
注意后缀自动机上串定位可以用倍增实现。

A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 800005
#define maxc 26
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 19
using namespace std;

int n;
char S[maxn];
int fa[maxn],f[lim][maxn],tr[maxn][maxc],len[maxn],pos[maxn],tot,last;
void ins(int c){
   
	int u = ++tot , p = last , q;
	len[last = u] = len[p] + 1;
	for(;~p && tr[p][c]==0;p=fa[p]) tr[p][c] = u;
	if(p==-1) fa[u] = 0;
	else if(len[q=tr[p][c]] == len[p] + 1) fa[u] = q;
	else{
   
		int v = ++tot;
		memcpy(tr[v],tr[q],sizeof tr[q]);
		fa[v] = fa[q] , len[v] = len[p] + 1;
		for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
		fa[q] = fa[u] = v;
	}
}

vector<int>G[maxn];int Le[maxn],na,nb;
LL dis[maxn];
int info[maxn],Prev[maxn<<2],to[maxn<<2],cst[maxn<<2],IN[maxn],cnt_e,cnt_p;
void Node(int u,int v,int c=0){
    Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c,IN[v]++; }
int in[maxn],ot[maxn];
bool cmp(const int &u,const int &v){
    return Le[u] == Le[v] ? u > v : Le[u] < Le[v]; }

int main(){
   
	
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	
	int T;
	fa[0] = -1;
	for(scanf("%d",&T);T--;){
   
		scanf("%s",S+1);
		n = strlen(S+1);
		reverse(S+1,S+n+1);
		rep(i,1,n) ins(S[i]-'a'),pos[i]=last;
		rep(i,1,tot) f[0][i] = fa[i];
		rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
		scanf("%d",&na);
		rep(i,1,na){
   
			int l,r;
			scanf("%d%d",&l,&r);
			swap(l,r);
			l = n - l + 1 , r = n - r + 1;
			Le[i] = r - l + 1;
			int u = pos[r];
			per(j,lim-1,0) if(len[f[j][u]] >= Le[i])
				u = f[j][u];
			G[u].push_back(i);
		}
		scanf("%d",&nb);
		rep(i,1,nb){
   
			int l,r;
			scanf("%d%d",&l,&r);
			swap(l,r);
			l = n - l + 1 , r = n - r + 1;
			Le[i+na] = r - l + 1;
			int u = pos[r];
			per(j,lim-1,0) if(len[f[j][u]] >= Le[i+na])
				u = f[j][u];
			G[u].push_back(i+na);
		}
		cnt_p = na + nb;
		rep(i,1,tot){
   
			sort(G[i].begin(),G[i].end(),cmp);
			in[i] = ++cnt_p;
			int p = in[i];
			rep(j,0,G[i].size()-1){
   
				if(G[i][j] <= na) 
					Node(p,G[i]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值