「ZJOI2017」字符串 (Hash)(分块)(线段树)

10 篇文章 0 订阅
2 篇文章 0 订阅

L O J LOJ LOJ 传送门

  • 题解:
    我们考虑用线段树维护最小后缀的出现位置,那么需要考虑如何合并两个区间
    发现最小后缀可能是前面区间的某一个后缀加上后面的一整个字符串,注意这个某一个后缀并不一定是前面区间的最小后缀,于是我们需要维护可能后缀的集合。
  • 定义:一个 i i i 的 “ k k k-后缀” 指的是字符串 S [ i . . . k ] S[i...k] S[i...k](以下用 S i S_i Si 表示),一个 i i i k k k-后缀是好的当且仅当在 k k k 后方可以添加一个字符串 T T T 使得 S [ i . . . k ] + T ≤ S [ j . . . k ] + T ( ∀ j ) S[i...k]+T\le S[j...k]+T(\forall j) S[i...k]+TS[j...k]+T(j)
    那么我们需要做的就是维护好的 k k k 后缀集合
  • 引理:令 i i i k k k 后缀的长度为 ∣ i ∣ = k − i + 1 |i|=k-i+1 i=ki+1,如果 i , j i,j i,j 均是好的 k k k 后缀,那么有 ∣ i ∣ ≥ 2 ∗ ∣ j ∣ |i|\ge 2*|j| i2j
    证明:考虑反证法,不妨令 ∣ i ∣ < 2 ∗ ∣ j ∣ |i|<2*|j| i<2j,首先 j j j k k k 后缀是 i i i k k k 后缀的前缀,那么容易发现 i i i k k k 后缀有一个长度为 ∣ i ∣ − ∣ j ∣ |i|-|j| ij 的循环,设 T T T 可以使 S j S_j Sj 为最小后缀,那么有 S i + T ≥ S j + T S_i+T\ge S_j+T Si+TSj+T
    去掉 ∣ i ∣ − ∣ j ∣ |i|-|j| ij 的循环,有 S j + T ≥ S j + ∣ i ∣ − ∣ j ∣ + T S_j+T\ge S_{j+|i|-|j|}+T Sj+TSj+ij+T S j S_j Sj 为最小后缀矛盾
  • 有了这个引理,我们可以知道一个好的 k k k-后缀集合大小是 l o g ∣ S ∣ log|S| logS 的,于是我们可以用线段树维护这么一个集合,单次修改需要合并 l o g ( n ) log(n) log(n) 次,合并需要支持比较两个后缀的大小,比较 l o g ( n ) log(n) log(n) 次,考虑二分 + h a s h hash hash,如果能把 h a s h hash hash 查询做到 O ( 1 ) O(1) O(1),那么复杂度就是 O ( M l o g ( n ) 3 ) O(Mlog(n)^3) O(Mlog(n)3),于是我们分块维护 h a s h hash hash (前缀和) 即可,复杂度 O ( n l o g ( n ) 2 + M l o g ( n ) 3 + M n ) O(nlog(n)^2+Mlog(n)^3+M\sqrt n) O(nlog(n)2+Mlog(n)3+Mn )
  • 主要突破口还是基于考虑好后缀的合并,然后发现更多的性质,挺巧妙的!
    代码也不难写 ,写完之后一发得了 70,不知道为什么 _ _ u i n t 128 _ t \_\_uint128\_t __uint128_t 才能过而 u n s i g n e d   l o n g   l o n g unsigned\ long \ long unsigned 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FSYo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值