LOJ #2572. 「ZJOI2017」字符串

题目链接

LOJ #2572. 「ZJOI2017」字符串

题目大意

一个长度为 \(n\) 的字符串 \(s\),字符集为 \(|x|\leq 10^9\) 的整数,有 \(q\) 次操作,分两种:

  • 输入 \(l,r,d\),对于 \(l\leq i\leq r\),将 \(s[i]\) 修改为 \(s[i]+d\)
  • 输入 \(l,r\),输出子串 \(s[l..r]\) 的字典序最小的后缀的左端点。

\(n\leq 2\times 10^5,q\leq 3\times 10^4\)\(|d|\leq 10^3,|s_i|\leq 10^8\)

思路

有一个很厉害的结论

Significant Suffixes Log Theory

称一个串 \(s\) 的 Significant Suffixes 为满足 \(\displaystyle\exists v,t=\arg \min_{u\in \text{suffix}(s)} \{tv\}\) 的后缀的集合,即在拼上一个串 \(v\) 后,\(t\)\(sv\) 的最小后缀。该 Theory 断言 \(s\) 的 Significant Suffixes 数量 \(\leq \log|s|\)

证明:对于两个属于 Significant Suffixes 的后缀 \(a,b\)\(|a|\geq |b|\),容易由定义推出 \(b\)\(a\) 的前缀,从而 \(a\) 有一个 \(|a|-|b|\) 的周期,若 \(2|b|>|a|\),则 \(a,b\) 可以分别表示 \(TTc\)\(Tc\),由于存在 \(v\) 满足 \(Tcv<TTcv\),则 \(cv<Tcv\)\(cv\) 才应该是最小后缀,矛盾。从而 \(|a|\geq 2|b|\),所以 \(s\) 的 Significant Suffixes 数量不超过 \(\log s\)


此题中所求的 \(s[l..r]\) 的字典序最小的后缀显然在其 Significant Suffixes 中,于是考虑用线段树维护字符串的 Significant Suffixes 集合,每个节点就存储对应的串的集合,当 \(l=r\) 时,\(a\) 的集合即自己本身。考虑合并节点信息,对于左右两个节点对应的串 \(u,v\),因为线段树上有 \(0\leq|v|-|u|\leq1\),所以 \(u\) 的集合中至多有一个是在 \(uv\) 集合中的,对于 \(u\) 集合中的两个后缀 \(a,b,|a|\geq |b|\),若 \(bv\)\(av\) 的后缀,则根据证明应保留 \(a\),否则保留 \(av,bv\) 中字典序较小的对应的那个。

找出 \(u\) 集合中唯一可能的后缀,然后与 \(|v|\) 集合合并即可,注意这里并不需要重新检查哪些不在 Significant Suffixes 中,我们只需要保证候选集合在 \(O(\log |s|)\) 的量级便可。在查询信息时,找出 \(s[l..r]\) 在线段树上对应的区间的所有候选集合,是 \(O(\log^2 n)\) 个,在其中找出最小的那个就行了。

对于判断两个串的大小关系,结合题目的修改方式容易想到用字符串哈希去维护,用线段树支持 \(O(\log n)\) 修改 \(O(\log n)\) 查询哈希值,然后二分两串 \(lcp\) 判断大小关系即可,单次 \(O(\log^2n)\)。这样总复杂度是 \(O(n\log^3n+m\log^4 n)\),有点恐怖。不过注意到查询的贡献比修改多的多,考虑用分块去替代线段树,通过维护块的左侧之和与整体加标记,可以做到 \(O(\sqrt n)\) 修改 \(O(1)\) 查询。

这样总时间复杂度便为 \(O(n\log^2n+m\sqrt n+m\log^3 n)\)

Code

数据卡自然溢出,双哈希又卡常,交了两页才过。

#include<iostream>
#include<fstream>
#include<vector>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 210022
#define B 460
#define Base 1000000007
#define ull __uint128_t
using namespace std;

int n, q, s[N];

struct String_Hash{
    ull pre[N/B], sum[N/B], val[N], a[N];
    ull pow[N], con[N];
    
    ull Pre(int b){
        if(b == 0) return 0;
        return pre[b] = pre[b-1] * pow[B] + val[b*B-1] + sum[b-1] * con[B-1];
    }
    void init(){ 
        pow[0] = 1, con[0] = 1;
        rep(i,1,n) pow[i] = pow[i-1] * Base, con[i] = con[i-1] + pow[i];
        rep(i,1,n) a[i] = s[i] + 2e8;
        rep(b,0,n/B){
            pre[b] = Pre(b), val[b*B] = a[b*B];
            rep(i,b*B+1,(b+1)*B-1) val[i] = val[i-1] * Base + a[i];
        }
    }
    void update(int l, int r, int k){
        if(l/B == r/B){
            int cur = l/B;
            rep(i,l,(cur+1)*B-1) 
                a[i] += (i <= r ? k : 0), val[i] = (i%B ? val[i-1] : 0) * Base + a[i];
            rep(b,cur+1,n/B) pre[b] = Pre(b);
            return;
        }
        rep(i,l,(l/B+1)*B-1) val[i] = (i%B ? val[i-1] : 0) * Base + (a[i] += k);
        rep(i,(r/B)*B,(r/B+1)*B-1) val[i] = (i%B ? val[i-1] : 0) * Base + (a[i] += (i <= r ? k : 0));
        rep(b,l/B+1,n/B) sum[b] += (b <= r/B-1 ? k : 0), pre[b] = Pre(b);
    }

    ull get(int pos){
        int b = pos/B, it = pos%B;
        return pre[b] * pow[it+1] + val[pos] + sum[b] * con[it];
    }
    bool equal(int l1, int r1, int l2, int r2){
        return get(r1) - get(l1-1) * pow[r1-l1+1] == get(r2) - get(l2-1) * pow[r2-l2+1];
    }
    int cmp(int l1, int r1, int l2, int r2){
        int n = r1-l1+1, m = r2-l2+1;
        int l = 0, r = min(n, m), mid;
        while(l < r){
            mid = (l+r+1)>>1;
            if(equal(l1, l1+mid-1, l2, l2+mid-1)) l = mid;
            else r = mid-1;
        }
        if(l == min(n, m)) return n == m ? 0 : (n < m ? -1 : 1);
        else{
            ull x = a[l1+l] + sum[(l1+l)/B], y = a[l2+l] + sum[(l2+l)/B];
            return x == y ? 0 : (x < y ? -1 : 1);
        }
    }
} Hash;

struct SegmentTree{
    vector<int> t[N<<2];

    vector<int> merge(vector<int> a, vector<int> b, int l, int mid, int r){
        int rem = a.front();
        for(int pos : a){
            if(pos == rem) continue;
            if(Hash.cmp(pos, r, rem, rem+r-pos) && Hash.cmp(pos, r, rem, r) == -1) rem = pos;
        }
        vector<int> ret = {rem};
        for(int pos : b) ret.push_back(pos);
        return ret;
    }

    void build(int x, int l, int r){
        if(l == r){ t[x] = {l}; return; }
        int mid = (l+r)>>1;
        build(x*2, l, mid), build(x*2+1, mid+1, r);
        t[x] = merge(t[x*2], t[x*2+1], l, mid, r);
    }

    void update(int x, int l, int r, int L, int R){
        if(l >= L && r <= R) return;
        int mid = (l+r)>>1;
        if(mid >= L) update(x*2, l, mid, L, R);
        if(mid < R) update(x*2+1, mid+1, r, L, R);
        t[x] = merge(t[x*2], t[x*2+1], l, mid, r);
    }

    vector<int> query(int x, int l, int r, int L, int R){
        if(l >= L && r <= R) return t[x];
        int mid = (l+r)>>1;
        if(mid >= R) return query(x*2, l, mid, L, R);
        if(mid < L) return query(x*2+1, mid+1, r, L, R);
        vector<int> a = query(x*2, l, mid, L, R), b = query(x*2+1, mid+1, r, L, R);
        for(int k : b) a.push_back(k);
        return a;
    }
} T;

int main(){
    ios::sync_with_stdio(false);
    cin>>n>>q;
    rep(i,1,n) cin>>s[i];
    Hash.init(), T.build(1, 1, n);

    int type, l, r, d;
    while(q--){
        cin>>type>>l>>r;
        if(type == 1) cin>>d, Hash.update(l, r, d), T.update(1, 1, n, l, r);
        else{
            vector<int> vec = T.query(1, 1, n, l, r);
            int ans = 0;
            for(int pos : vec)
                if(ans == 0 || Hash.cmp(pos, r, ans, r) == -1) ans = pos;
            cout<< ans <<endl;
        }
    }
    return 0;
}
posted @ 2022-02-16 20:55  Neal_lee  阅读(89)  评论(0编辑  收藏  举报