「ZJOI2017」字符串 (Hash)(分塊)(線段樹)

LOJLOJ 傳送門

  • 題解:
    我們考慮用線段樹維護最小後綴的出現位置,那麼需要考慮如何合併兩個區間
    發現最小後綴可能是前面區間的某一個後綴加上後面的一整個字符串,注意這個某一個後綴並不一定是前面區間的最小後綴,於是我們需要維護可能後綴的集合。
  • 定義:一個 ii 的 “kk-後綴” 指的是字符串 S[i...k]S[i...k](以下用 SiS_i 表示),一個 iikk-後綴是好的當且僅當在 kk 後方可以添加一個字符串 TT 使得 S[i...k]+TS[j...k]+T(j)S[i...k]+T\le S[j...k]+T(\forall j)
    那麼我們需要做的就是維護好的 kk 後綴集合
  • 引理:令 iikk 後綴的長度爲 i=ki+1|i|=k-i+1,如果 i,ji,j 均是好的 kk 後綴,那麼有 i2j|i|\ge 2*|j|
    證明:考慮反證法,不妨令 i<2j|i|<2*|j|,首先 jjkk 後綴是 iikk 後綴的前綴,那麼容易發現 iikk 後綴有一個長度爲 ij|i|-|j| 的循環,設 TT 可以使 SjS_j 爲最小後綴,那麼有 Si+TSj+TS_i+T\ge S_j+T
    去掉 ij|i|-|j| 的循環,有 Sj+TSj+ij+TS_j+T\ge S_{j+|i|-|j|}+TSjS_j 爲最小後綴矛盾
  • 有了這個引理,我們可以知道一個好的 kk-後綴集合大小是 logSlog|S| 的,於是我們可以用線段樹維護這麼一個集合,單次修改需要合併 log(n)log(n) 次,合併需要支持比較兩個後綴的大小,比較 log(n)log(n) 次,考慮二分 + hashhash,如果能把 hashhash 查詢做到 O(1)O(1),那麼複雜度就是 O(Mlog(n)3)O(Mlog(n)^3),於是我們分塊維護 hashhash (前綴和) 即可,複雜度 O(nlog(n)2+Mlog(n)3+Mn)O(nlog(n)^2+Mlog(n)^3+M\sqrt n)
  • 主要突破口還是基於考慮好後綴的合併,然後發現更多的性質,挺巧妙的!
    代碼也不難寫 ,寫完之後一發得了 70,不知道爲什麼 __uint128_t\_\_uint128\_t 才能過而 unsigned long longunsigned\ long \ long 不行,可能是衝突了之類的(霧)
#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;
}
cs int N = 2e5 + 50;
typedef __uint128_t ull;
int n, m;
namespace Hash{
	int blk[N], l[N], r[N], vl[N], add[N], ct;
	ull pw[N], Spw[N], sum[N], tag[N];
	cs int Base = 1e9 + 7;
	void Build(){
		int S = sqrt(n);
		for(int i = 1; i <= n; i++) blk[i] = (i-1) / S + 1;
		for(int i = 1; i <= n; i += S) l[++ct] = i, r[ct] = i + S - 1; r[ct] = n;
		pw[0] = Spw[0] = 1;
		for(int i = 1; i <= n; i++) 
			pw[i] = pw[i-1] * Base, Spw[i] = Spw[i-1] + pw[i], sum[i] = sum[i-1] + (ull)vl[i] * pw[i];
	}
	void modify(int ql, int qr, int d){
		int pl = blk[ql], pr = blk[qr];
		if(pl + 1 >= pr){
			for(int i = ql; i <= qr; i++){
				vl[i] += d;
				sum[i] += d * (Spw[i] - Spw[ql - 1]);
			}
		}
		else{
			for(int i = ql; i <= r[pl]; i++)
				vl[i] += d, sum[i] += d * (Spw[i] - Spw[ql - 1]);
			for(int i = pl + 1; i < pr; i++)
				add[i] += d, tag[i] += d * (Spw[l[i] - 1] - Spw[ql - 1]);
			for(int i = l[pr]; i <= qr; i++)
				vl[i] += d, sum[i] += d * (Spw[i] - Spw[ql - 1]);
		}
		ull dlt = d * (Spw[qr] - Spw[ql - 1]);
		for(int i = qr + 1; i <= r[pr]; i++) sum[i] += dlt;
		for(int i = pr + 1; i <= ct; i++) tag[i] += dlt;
	}
	ull Get(int x){ return sum[x] + tag[blk[x]] + add[blk[x]] * (Spw[x] - Spw[l[blk[x]]-1]); }
	int val(int x){ return vl[x] + add[blk[x]]; }
	int lcp(int x, int y){
		if(x > y) swap(x, y);
		int l = 0, r = n - y + 1; ull vx = Get(x - 1), vy = Get(y - 1);
		while(l < r){
			int mid = (l+r+1) >> 1;
			if((Get(x + mid - 1) - vx) * pw[y - x] == Get(y + mid - 1) - vy) l = mid;
			else r = mid - 1;
		} return l;
	}
}
struct data{
	int l, r; 
	vector<int> S;
	data(int _l = 0, int _r = 0){ S.clear(); l = _l; r = _r; }
};
data operator + (cs data &A, cs data &B){
	data as(A.l, B.r);
	for(int x : A.S){
		bool FLAG = true;
		while(as.S.size()){
			int y = as.S.back();
			int lcp = Hash :: lcp(x, y);
			if(x + lcp - 1 >= B.r) break;
			if(Hash :: val(x + lcp) > Hash :: val(y + lcp)){ FLAG = false; break; }
			as.S.pop_back();
		} 
		if(FLAG && (as.S.empty() || B.r - x + 1 <= x - as.S.back())) as.S.pb(x);
	}
	for(int x : B.S){
		bool FLAG = true;
		while(as.S.size()){
			int y = as.S.back();
			int lcp = Hash :: lcp(x, y);
			if(x + lcp - 1 >= B.r) break;
			if(Hash :: val(x + lcp) > Hash :: val(y + lcp)){ FLAG = false; break; }
			as.S.pop_back();
		} 
		if(FLAG && (as.S.empty() || B.r - x + 1 <= x - as.S.back())) as.S.pb(x);
	} return as;
}
namespace SGT{
	cs int N = ::N << 2;
	#define mid ((l+r)>>1)
	data vl[N];
	void pushup(int x){ vl[x] = vl[x<<1] + vl[x<<1|1]; }
	void build(int x, int l, int r){
		if(l == r){ 
			vl[x].l = l; vl[x].r = r;
			vl[x].S.pb(l); return;
		}
		build(x<<1, l, mid); build(x<<1|1, mid+1, r);
		pushup(x);
	}
	void modify(int x, int l, int r, int L, int R){
		if(L <= l && r <= R) return;
		if(L<=mid) modify(x<<1, l, mid, L, R);
		if(R>mid) modify(x<<1|1, mid+1, r, L, R); pushup(x);
	}
	data query(int x, int l, int r, int L, int R){
		if(L <= l && r <= R) return vl[x];
		if(R<=mid) return query(x<<1, l, mid, L, R);
		else if(L>mid) return query(x<<1|1, mid+1, r, L, R);
		return query(x<<1, l, mid, L, R) + query(x<<1|1, mid+1, r, L, R);
	}
	#undef mid
}
int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i++) Hash :: vl[i] = read() + 5e8;
	Hash :: Build();
	SGT :: build(1, 1, n);
	while(m--){
		int op = read(), l = read(), r = read();
		if(op == 1){
			int d = read(); 
			Hash :: modify(l, r, d);
			SGT :: modify(1, 1, n, l, r);
		}
		if(op == 2)
		cout << SGT :: query(1, 1, n, l, r).S.back() << '\n';
	} return 0;
}
發佈了651 篇原創文章 · 獲贊 98 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章