「九省联考 2018」制胡窜 (SAM)(线段树合并)(分类讨论)

L O J LOJ 传送门

题解:
好题啊,除的写起来伤心情……
首先转换一下题意,找到这个串的所有出现位置,这个可以用 S A M SAM 加倍增实现,然后砍两刀使得每一个出现位置都被砍断,下面我们对这两刀怎么砍分类讨论:

一些规定: 令每个串的出现位置为 l i , r i l_i,r_i ,出现的次数为 m m ,令 L L r 1 r_1 R R l m l_m ,一个点的(不严格可以取等)前驱为它前面第一个出现的串的 e n d p o s endpos r i r_i ,一个点的后继(可以取等)为后面第一个 r i r_i ,符号分别为 p r e , s u f pre,suf ,本次询问的覆盖范围为 [ l 1 , r m ] [l_1,r_m] ,本次询问串的长度为 l e n len ,在 i i 切一刀指的是将 ( i , i + 1 ) (i,i+1) 切断

  • C a s e   1 : Case\ 1:
    R R 的前驱的左端点与 S l 1 , r 1 S_{l_1,r_1} 不相交,或者只交了一个字符,这种情况显然方案为 0

  • C a s e   2 : Case\ 2:
    L > R L> R ,这种情况中可以分为几类:第一刀没切完第二刀把剩下的切完,第一刀切完,第二刀也切完或切第一刀左边的一部分或随便切一个地方( [ l 1 , r m ] \notin [l_1,r_m]
    发现这个贡献可以分段统计,枚举第一刀的端点 [ l i , l i + 1 ) [l_i,l_{i+1}) ,那么可行的第二刀可以切在 [ R , r i + 1 ) [R,r_{i+1})
    这个的方案数是 i ( l i + 1 l i ) ( r i + 1 R ) \sum_i (l_{i+1}-l_i)(r_{i+1}-R) ,第一刀切在中间,需要加上不同于刚刚那种情况的答案,分都在中间还是只有第一刀在中间讨论,那么可以得到方案数为 ( R L 2 ) + ( R L ) ( n l e n ) \binom{R-L}{2}+(R-L)(n-len)
    那么我们可以考虑用线段树维护 i ( l i + 1 l i ) , ( r i + 1 i l i + 1 l i ) \sum_i(l_{i+1}-l_i),(r_{i+1}\sum_i l_{i+1}-l_i)

  • C a s e   3 : Case\ 3:
    L R L\le R ,这种情况不存在一刀端,必须两刀配合完成,同样考虑当前选择 [ l i , l i + 1 ) [l_i,l_{i+1})
    那么需要满足 r i + 1 > R r_{i+1}>R ,同时 l i + 1 < L l_{i+1}<L ,于是我们可以在线段树上完成这样一个区间查询
    假设最后一个合法的区间为 k k l k + 1 < L l_{k+1}<L ),那么 [ l k + 1 , L ) [l_{k+1},L) 是可以切的,找到这个 k k 可以通过查 L + l e n 1 L+len-1 的前驱和 L + l e n L+len 后继来实现,贡献为 ( p r e ( L + l e n 1 ) ) ( s u f R ) (pre-(L+len-1))*(suf-R)
    同时需要找到第一个满足 r i + 1 > R r_{i+1}>R 的区间最为起始节点,这个可以通过 R R 的前缀找到 i i

然后就做完啦,个人感觉讨论得还比较清楚


#include<bits/stdc++.h>
#define cs const
#define pb push_back
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;
}
typedef long long ll;
cs int N = 3e5 + 50;
cs int INF = 1e9;
int n, m, ps[N], rt[N]; ll ans[N];
char S[N];
struct query{ 
	int l, r, c; 
	query(int _l=0, int _r=0, int _c=0){ l=_l; r=_r; c=_c; }
};
vector<query> qry[N];
vector<int> G[N]; int fa[N][20];
namespace SGT{
	cs int N = ::N * 40;
	int nd,ls[N],rs[N],mi[N],mx[N]; ll v1[N],v2[N];
	#define mid ((l+r)>>1)
	void pushup(int x){
		if(!ls[x]){ mi[x]=mi[rs[x]]; mx[x]=mx[rs[x]]; v1[x]=v1[rs[x]]; v2[x]=v2[rs[x]]; return; }
		if(!rs[x]){ mi[x]=mi[ls[x]]; mx[x]=mx[ls[x]]; v1[x]=v1[ls[x]]; v2[x]=v2[ls[x]]; return; }
		mi[x]=mi[ls[x]]; mx[x]=mx[rs[x]];
		int delta = mi[rs[x]]-mx[ls[x]];
		v1[x]=v1[ls[x]]+v1[rs[x]]+(ll)delta*mi[rs[x]];
		v2[x]=v2[ls[x]]+v2[rs[x]]+delta;
	}
	void ins(int &x, int l, int r, int p){
		if(!x) x=++nd; mi[x]=mx[x]=p; if(l==r) return;
		(p<=mid)?ins(ls[x],l,mid,p):ins(rs[x],mid+1,r,p);
	}
	void merge(int &x, int y){
		if(!x||!y){ x|=y; return; }
		merge(ls[x],ls[y]); merge(rs[x],rs[y]); pushup(x);
	}
	int qrymi(int x, int l, int r, int L, int R){
		if(!x) return INF;
		if(L<=l&&r<=R) return mi[x]; int as=INF;
		if(L<=mid) as=min(as,qrymi(ls[x],l,mid,L,R)); if(as!=INF) return as;
		if(R>mid) as=min(as,qrymi(rs[x],mid+1,r,L,R)); return as;
	}
	int qrymx(int x, int l, int r, int L, int R){
		if(!x) return -INF;
		if(L<=l&&r<=R) return mx[x]; int as=-INF;
		if(R>mid) as=max(as,qrymx(rs[x],mid+1,r,L,R)); if(as!=-INF) return as;
		if(L<=mid) as=max(as,qrymx(ls[x],l,mid,L,R)); return as;
	}
	ll as1, as2, trans;
	void query(int x, int l, int r, int L, int R){
		if(!x) return; 
		if(L<=l && r<=R){
			ll delta = mi[x] - trans; trans = mx[x];
			as1 += v1[x] + delta * mi[x];
			as2 += v2[x] + delta; return;
		}
		if(L<=mid) query(ls[x],l,mid,L,R);
		if(R>mid) query(rs[x],mid+1,r,L,R); 
	}
	ll qry(int x, int l, int r, int ps, int R){
		trans=ps; as1=as2=0; query(x,1,n,l,r); 
		return as1-as2*R;
	}
	ll calc(int x, int R){ return v1[x]-(ll)v2[x]*R; }
}
namespace SAM{
	int ch[N][10],lk[N],len[N],r[N],nd=1,las=1;
	int extend(int k, int c){
		int p=las, now=++nd; len[now]=len[las]+1; r[now]=k;
		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[q]));
				lk[q]=lk[now]=cl; 
				for(;p&&ch[p][c]==q;p=lk[p]) ch[p][c]=cl;
			}
		} las = now; return now;
	}
	void ready(){ 
		for(int i=1; i<=nd; i++) G[lk[i]].pb(i); 
		for(int i=1; i<=nd; i++) if(r[i]) SGT::ins(rt[i],1,n,r[i]);
	}
}
void pre_dfs(int u){
	for(int i=1; i<=18; i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v : G[u]) fa[v][0]=u, pre_dfs(v);
}
int jump(int l, int r){
	int nx = ps[r], len = r-l+1;
	for(int i=18; ~i; i--) if(SAM::len[fa[nx][i]]>=len) nx=fa[nx][i];
	return nx;
}
ll C2(int x){ return (ll)x*(x-1)/2;}
void work(int u){
	for(int v : G[u]){
		work(v); 
		SGT::merge(rt[u],rt[v]);
	}
	int L = SGT::mi[rt[u]], ed = SGT::mx[rt[u]];
	for(auto t : qry[u]){
		int len = t.r - t.l + 1, R = ed - len + 1;
		if(R<=L) ans[t.c] = SGT::calc(rt[u],R) + C2(L-R) + (ll)(L-R)*(n-len);
		else{
			int pos = SGT::qrymx(rt[u],1,n,1,R);
			if(L+len-1<=pos) continue;
			ans[t.c] = SGT::qry(rt[u],R+1,L+len-1,pos,R);
			int rp = SGT::qrymi(rt[u],1,n,L+len,n), lp = SGT::qrymx(rt[u],1,n,1,L+len-1);
			if(rp!=INF&&lp!=-INF) if(rp>R) ans[t.c]+=(ll)(rp-R)*(L-(lp-len+1));
		}
	}
}
int main(){
	n = read(), m = read();
	scanf("%s",S+1); 
	for(int i=1; i<=n; i++) ps[i]=SAM::extend(i,S[i]-'0'); 
	SAM::ready(); pre_dfs(0);
	for(int i=1; i<=m; i++){
		int l=read(), r=read(), nx=jump(l,r);
		qry[nx].push_back(query(l,r,i));
	}
	work(0); ll sm = C2(n-1);
	for(int i=1; i<=m; i++) cout << sm-ans[i] << '\n';
	return 0;
}
发布了634 篇原创文章 · 获赞 98 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/104215031