「九省聯考 2018」制胡竄 (SAM)(線段樹合併)(分類討論)

LOJLOJ 傳送門

題解:
好題啊,除的寫起來傷心情……
首先轉換一下題意,找到這個串的所有出現位置,這個可以用 SAMSAM 加倍增實現,然後砍兩刀使得每一個出現位置都被砍斷,下面我們對這兩刀怎麼砍分類討論:

一些規定: 令每個串的出現位置爲 li,ril_i,r_i,出現的次數爲 mm,令 LLr1r_1RRlml_m,一個點的(不嚴格可以取等)前驅爲它前面第一個出現的串的 endposendposrir_i,一個點的後繼(可以取等)爲後面第一個 rir_i,符號分別爲 pre,sufpre,suf,本次詢問的覆蓋範圍爲 [l1,rm][l_1,r_m],本次詢問串的長度爲 lenlen,在 ii 切一刀指的是將 (i,i+1)(i,i+1) 切斷

  • Case 1:Case\ 1:
    RR 的前驅的左端點與 Sl1,r1S_{l_1,r_1} 不相交,或者只交了一個字符,這種情況顯然方案爲 0

  • Case 2:Case\ 2:
    L>RL> R,這種情況中可以分爲幾類:第一刀沒切完第二刀把剩下的切完,第一刀切完,第二刀也切完或切第一刀左邊的一部分或隨便切一個地方([l1,rm]\notin [l_1,r_m]
    發現這個貢獻可以分段統計,枚舉第一刀的端點 [li,li+1)[l_i,l_{i+1}),那麼可行的第二刀可以切在 [R,ri+1)[R,r_{i+1})
    這個的方案數是 i(li+1li)(ri+1R)\sum_i (l_{i+1}-l_i)(r_{i+1}-R),第一刀切在中間,需要加上不同於剛剛那種情況的答案,分都在中間還是隻有第一刀在中間討論,那麼可以得到方案數爲 (LR2)+(LR)(nlen)\binom{L-R}{2}+(L-R)(n-len)
    那麼我們可以考慮用線段樹維護 i(li+1li),(ri+1ili+1li)\sum_i(l_{i+1}-l_i),(r_{i+1}\sum_i l_{i+1}-l_i)

  • Case 3:Case\ 3:
    LRL\le R,這種情況不存在一刀端,必須兩刀配合完成,同樣考慮當前選擇 [li,li+1)[l_i,l_{i+1})
    那麼需要滿足 ri+1>Rr_{i+1}>R,同時 li+1<Ll_{i+1}<L ,於是我們可以在線段樹上完成這樣一個區間查詢
    假設最後一個合法的區間爲 kklk+1<Ll_{k+1}<L),那麼 [lk+1,L)[l_{k+1},L) 是可以切的,找到這個 kk 可以通過查 L+len1L+len-1 的前驅和 L+lenL+len 後繼來實現,貢獻爲 (pre(L+len1))(sufR)(pre-(L+len-1))*(suf-R)
    同時需要找到第一個滿足 ri+1>Rr_{i+1}>R 的區間最爲起始節點,這個可以通過 RR 的前綴找到 ii

然後就做完啦,個人感覺討論得還比較清楚


#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;
}
發佈了651 篇原創文章 · 獲贊 98 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章