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]+T≤S[j...k]+T(∀j)
那么我们需要做的就是维护好的 k k k 后缀集合
- 引理:令 i i i 的 k k k 后缀的长度为 ∣ i ∣ = k − i + 1 |i|=k-i+1 ∣i∣=k−i+1,如果 i , j i,j i,j 均是好的 k k k 后缀,那么有 ∣ i ∣ ≥ 2 ∗ ∣ j ∣ |i|\ge 2*|j| ∣i∣≥2∗∣j∣
证明:考虑反证法,不妨令 ∣ i ∣ < 2 ∗ ∣ j ∣ |i|<2*|j| ∣i∣<2∗∣j∣,首先 j j j 的 k k k 后缀是 i i i 的 k k k 后缀的前缀,那么容易发现 i i i 的 k k k 后缀有一个长度为 ∣ i ∣ − ∣ j ∣ |i|-|j| ∣i∣−∣j∣ 的循环,设 T T T 可以使 S j S_j Sj 为最小后缀,那么有 S i + T ≥ S j + T S_i+T\ge S_j+T Si+T≥Sj+T,
去掉 ∣ i ∣ − ∣ j ∣ |i|-|j| ∣i∣−∣j∣ 的循环,有 S j + T ≥ S j + ∣ i ∣ − ∣ j ∣ + T S_j+T\ge S_{j+|i|-|j|}+T Sj+T≥Sj+∣i∣−∣j∣+T 与 S j S_j Sj 为最小后缀矛盾
- 有了这个引理,我们可以知道一个好的 k k k-后缀集合大小是 l o g ∣ S ∣ log|S| log∣S∣ 的,于是我们可以用线段树维护这么一个集合,单次修改需要合并 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 不行,可能是冲突了之类的(雾)
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;
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);
}
}
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;
}