「HAOI2018」字串覆盖 (SAM)(Hash)(倍增)

L O J LOJ LOJ传送门

  • 挺好的题,就是提示得有些明显,很显然对 r − l ≥ 2000 r-l\ge 2000 rl2000 r − l ≤ 50 r-l\le 50 rl50 有两种不同的做法
  • 一个比较容易发现的贪心是我们选尽量前面的匹配,匹配过后再寻找下一个位置
    那么就有一个做法是对第一个串建 S A M SAM SAM 倍增找到第二个串的结点,线段树合并 e n d p o s endpos endpos,在 e n d p o s endpos endpos 里面找一个 ( s + l e n , t ] (s+len,t] (s+len,t] 的匹配,然后再找下一个,显然只会匹配 ( t − s + 1 ) / ( r − l + 1 ) (t-s+1)/(r-l+1) (ts+1)/(rl+1) 次,那么 r − l ≥ 2000 r-l\ge2000 rl2000 的部分就可以解决掉,单次询问的复杂度是 l o g ( n ) ∗ n 2000 log(n)*\frac{n}{2000} log(n)2000n
  • r − l ≤ 50 r-l\le 50 rl50 :由于匹配次数很多,考虑倍增,我们对每一个长度预处理每一个末尾位置的下一个串以及后 2 j 2^j 2j 个串的位置,同时求出跳这么多步的贡献,由于空间开不下,对询问离线按长度递增处理
    预处理可以用 h a s h hash hash
    那么每次可以通过线段树找到第一个出现位置然后向后跳,单次询问的复杂度是 O ( l o g ( n ) 2 ) O(log(n)^2) O(log(n)2)
    预处理的复杂度是 O ( 50 ∗ n ∗ l o g ( n ) ) O(50*n*log(n)) O(50nlog(n))

好写好调 ,只有 5 k 5k 5k

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
	while(isdigit(ch)) cnt=cnt*10+(ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 1e5 + 50;
typedef long long ll;
int n, q, K, rt[N<<1], lg[N<<1];
namespace SGT{
	cs int N = ::N * 80;
	int ls[N], rs[N], sz[N], nd;
	#define mid ((l+r)>>1)
	void ins(int &x, int l, int r, int p){
		if(!x) x=++nd; ++sz[x]; if(l==r) return;
		(p<=mid)?ins(ls[x],l,mid,p):ins(rs[x],mid+1,r,p);
	}
	int merge(int x, int y){
		if(!x||!y) return x|y; int nx=++nd;
		ls[nx]=merge(ls[x],ls[y]);
		rs[nx]=merge(rs[x],rs[y]); 
		sz[nx]=sz[ls[nx]]+sz[rs[nx]]; return nx;
	}
	int query(int x, int l, int r, int L, int R){
		if(!x) return 0; if(l==r) return l; int as=0;
		if(L<=mid) as=query(ls[x],l,mid,L,R); if(as) return as;
		if(R>mid) as=query(rs[x],mid+1,r,L,R);  return as;
	}
}
namespace SAM{
	cs int N = ::N<<1;
	int ch[N][26], lk[N], len[N], r[N], nd=1, las=1;
	int fa[N][20],dep[N];
	void extend(int c, int k){
		int now=++nd, p=las; r[now]=k; len[now]=len[p]+1;
		for(;p&&!ch[p][c];p=lk[p]) ch[p][c]=now;
		if(!p) lk[now]=1;
		else{
			int q=ch[p][c]; 
			if(len[q]==len[p]+1) lk[now]=q;
			else{
				int cl=++nd; len[cl]=len[p]+1; lk[cl]=lk[q];
				memcpy(ch[cl],ch[q],sizeof(ch[cl]));
				for(;p&&ch[p][c]==q;p=lk[p]) ch[p][c]=cl;
				lk[q]=lk[now]=cl;
			}
		} las=now;
	} 
	void trans(int &u, int &l, int c){
		while(u&&!ch[u][c]) u=lk[u], l=len[u];
		if(!u) u=1, l=0; else u=ch[u][c], ++l;
	}
	void build(){
		static int bin[N], A[N];
		for(int i=1; i<=nd; i++) ++bin[len[i]];
		for(int i=1; i<=n; i++) bin[i]+=bin[i-1];
		for(int i=nd; i>=1; i--) A[bin[len[i]]--]=i;
		for(int i=nd; i>=2; i--){
			int x = A[i]; fa[x][0] = lk[x];
			if(r[x]) SGT::ins(rt[x],1,n,r[x]);
			rt[lk[x]]=SGT::merge(rt[lk[x]],rt[x]);
		} 
		for(int i=1; i<=nd; i++) dep[A[i]]=dep[lk[A[i]]]+1;
		for(int i=1; i<=nd; i++) 
		for(int j=1; j<=lg[dep[A[i]]]; j++)
		fa[A[i]][j]=fa[fa[A[i]][j-1]][j-1];
	}
	int jump(int x, int ln){
		for(int i=lg[dep[x]];~i;i--) 
		if(len[fa[x][i]]>=ln) x=fa[x][i]; return x;
	}
}
char S[N], T[N];
int ps[N], len[N];
namespace Subtask1{
	ll work(int s, int t, int l, int r){
		int x = ps[r], nl = r-l+1; if(len[r] < nl) return 0;
		x = SAM::jump(x, nl); int mat = s; ll as = 0; 
		while(1){
			if(mat+nl-1>t) break;
			int p = SGT::query(rt[x],1,n,mat+nl-1,t);
			if(!p) break; as+=(ll)(K-(p-nl+1)); mat=p+1;
		} return as;
	}
}
namespace Subtask2{
	typedef unsigned long long ull;
	int nxt[N][20]; ll sm[N][20];
	unordered_map<ull, int> mp;
	cs int Base = 5261023;
	ull haS[N], pw[N];
	ull GetS(int l, int r){ return haS[r]-haS[l-1]*pw[r-l+1]; }
	void init(int LEN){
		if(LEN>n) return; mp.clear();
		memset(nxt,0,sizeof(nxt));
		memset(sm,0,sizeof(sm));
		for(int i=n; i>=LEN; i--){
			if(i+LEN<=n) mp[GetS(i+1,i+LEN)]=i+LEN;
			nxt[i][0] = mp[GetS(i-LEN+1,i)];
			sm[i][0] = K-(i-LEN+1);
		}
		for(int i=n; i>=1; i--)
		for(int j=1; j<=lg[(n-i+1)/LEN]; j++){
			nxt[i][j]=nxt[nxt[i][j-1]][j-1];
			sm[i][j]=sm[i][j-1]+sm[nxt[i][j-1]][j-1];
		} 
	}
	void prework(){ 
		pw[0]=1; for(int i=1; i<=n; i++) pw[i]=pw[i-1]*Base;
		for(int i=1; i<=n; i++) haS[i]=haS[i-1]*Base+S[i];
	}
	ll work(int s, int t, int l, int r){
		int x = ps[r], LEN = r-l+1; if(len[r] < LEN) return 0;
		x = SAM::jump(x,LEN); int u=SGT::query(rt[x],1,n,s+LEN-1,t);
		if(!u) return 0; ll as=0;
		for(int j=lg[(n-u+1)/LEN];~j;j--)
		if(nxt[u][j]&&nxt[u][j]<=t) as+=sm[u][j], u=nxt[u][j];
		as+=(ll)K-(u-LEN+1);
		return as; 
	}
}
struct qry{ int s,t,l,r,c; }qr[N];
bool cmp(qry a, qry b){ return (a.r-a.l) < (b.r-b.l); }
int main(){
	n = read(), K = read();
	for(int i=2; i<=n+n; i++) lg[i]=lg[i>>1]+1;
	scanf("%s",S+1);
	scanf("%s",T+1);
	for(int i=1; i<=n; i++) SAM::extend(S[i]-'a',i); 
	ps[0]=1; len[0]=0;
	for(int i=1; i<=n; i++) 
	ps[i]=ps[i-1], len[i]=len[i-1], SAM::trans(ps[i],len[i],T[i]-'a');
	SAM::build();
	Subtask2::prework();
	q = read();
	for(int i=1; i<=q; i++){
		int s=read(), t=read(), l=read(), r=read();
		qr[i] = (qry){s,t,l,r,i};
	} 
	sort(qr+1, qr+q+1, cmp);
	static ll as[N];
	for(int i=1,las=0; i<=q; i++){
		int s=qr[i].s, t=qr[i].t, l=qr[i].l, r=qr[i].r;
		if(r-l+1<=50 && r-l+1!=las) Subtask2::init(r-l+1), las=r-l+1;
		if(r-l+1<=50) as[qr[i].c]=Subtask2::work(s,t,l,r);
		else as[qr[i].c]=Subtask1::work(s,t,l,r);
	} 
	for(int i=1; i<=q; i++) cout<<as[i]<<'\n';
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值