0
点赞
收藏
分享

微信扫一扫

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

alanwhy 2022-07-12 阅读 51

「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀 传送门​​

  • 题解:
    我们考虑用线段树维护最小后缀的出现位置,那么需要考虑如何合并两个区间
    发现最小后缀可能是前面区间的某一个后缀加上后面的一整个字符串,注意这个某一个后缀并不一定是前面区间的最小后缀,于是我们需要维护可能后缀的集合。
  • 定义:一个「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_02的 “「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03-后缀” 指的是字符串「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_04(以下用「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_05表示),一个「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_02「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03-后缀是好的当且仅当在「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后方可以添加一个字符串「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_09使得「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_10
    那么我们需要做的就是维护好的「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀集合
  • 引理:令「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_02「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀的长度为「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_14,如果「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_15均是好的「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀,那么有「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_17
    证明:考虑反证法,不妨令「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_18,首先「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_19「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀是「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_02「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀的前缀,那么容易发现「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_02「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03后缀有一个长度为「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_25的循环,设「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_09可以使「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_27为最小后缀,那么有「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_28
    去掉「ZJOI2017」字符串 (Hash)(分块)(线段树)_#define_25的循环,有「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_30「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_27为最小后缀矛盾
  • 有了这个引理,我们可以知道一个好的「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_03-后缀集合大小是「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_33的,于是我们可以用线段树维护这么一个集合,单次修改需要合并「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_34次,合并需要支持比较两个后缀的大小,比较「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_34次,考虑二分 +「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_36,如果能把「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_36查询做到「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_38,那么复杂度就是「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_39,于是我们分块维护「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_36(前缀和) 即可,复杂度「ZJOI2017」字符串 (Hash)(分块)(线段树)_i++_41
  • 主要突破口还是基于考虑好后缀的合并,然后发现更多的性质,挺巧妙的!
    代码也不难写 ,写完之后一发得了 70,不知道为什么「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_42才能过而「ZJOI2017」字符串 (Hash)(分块)(线段树)_后缀_43不行,可能是冲突了之类的(雾)
#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;
}


举报

相关推荐

0 条评论