1. 程式人生 > 其它 >CF438D The Child and Sequence

CF438D The Child and Sequence

傳送門

題意

維護一個支援區間取模,區間求和,單點修改的資料結構。

題解

考慮一個數被取模後至少變為原來的\(\frac 12\)(模數小於原數的情況下), 也就是說對於每個單點修改加入的數,只會被取log次模。

證明:對於一個數 \(x\),若模數 \(p\) 小於 \(\frac x2\),顯然符合,若模數大於 \(\frac x2\), 則結果等於 \(x - p > x - \frac x2\)

那麼如果沒有冗餘操作的情況下, 暴力的複雜度是正確的。
什麼是冗餘操作呢,我們的性質是一個數被取很多次模之後會變的很小,我們認為很小的數不需要被取模,
實際上就是說,對於小於模數的數就可以不用操作了, 也就是說如果我們每次取模的數都大於模數那麼複雜度就是正確的。

如果只是樸素的區間取模,對於一個區間中的每個數判斷一下是否大於模數,那麼就會有很多冗餘操作。
這時候一種顯然的想法是,我們可以使用排序樹上二分,找到每個區間中大於模數的數,對他們取模。
複雜度為 \(O(nlog^2n)\), 取模複雜度均攤下來是 \(O(nlogn)\) 的,每次找到大於模數的數,要在排序樹上找到對應區間,一共有\(O(logn)\)個塊, 每個塊上做二分\(O(logn)\)的, 寫到這我發現這個做法是不支援修改的, 寄。

學習了題解的炫酷做法後, 被炫酷了。
只要我們能夠只對所有大於模數的數取模,複雜度就正確,考慮線段樹上維護某個區間的最大值,如果這個區間的最大值大於模數,那麼這個區間裡肯定有要被取模的數,繼續遞迴,否則沒有就直接返回。這樣就可以找到所有合法的數了!對於每個數每次被修改,找到他需要經過 \(O(logn)\)

個區間,一個數要被修改 \(O(logn)\) 次, 總複雜度 \(O(nlog^2n)\)

實現


#include <iostream>
#include <cstdio>
#define l(o) (o*2)
#define r(o) (o*2+1)
#define ll long long
using namespace std;

int read(){
    int num=0, flag=1; char c=getchar();
    while(!isdigit(c) && c!='-') c=getchar();
    if(c == '-') c=getchar(), flag=-1;
    while(isdigit(c)) num=num*10+c-'0', c=getchar();
    return num*flag;
}

const int N = 2e5;
ll a[N];


int n, m;


struct SegTree{
    ll tree[N<<2], maxv[N<<2];

    void push_up(int o){
        tree[o] = tree[l(o)] + tree[r(o)];
        maxv[o] = max(maxv[l(o)], maxv[r(o)]);
    }

    void update(int o, int l, int r, int x, int v){
        if(l == r){
            tree[o] = v;
            maxv[o] = v; 
            return ;
        }

        int mid = (l+r)/2;
        if(x <= mid) update(l(o), l, mid, x, v);
        else update(r(o), mid+1, r, x, v);

        push_up(o);
    }

    ll query(int o, int l, int r, int ql, int qr){
        if(ql<=l && r<=qr) return tree[o];
        ll res = 0; int mid = (l+r)/2;
        if(ql <= mid) res += query(l(o), l, mid, ql, qr);
        if(qr > mid) res += query(r(o), mid+1, r, ql, qr);
        return res;
    }

    void mod(int o, int l, int r, int ql, int qr, int v){
        if(maxv[o] < v) return ;
        if(l == r) {
            tree[o] %= v;
            maxv[o] %= v;
            return ;
        }

        int mid = (l+r)/2;
        if(ql <= mid) mod(l(o), l, mid, ql, qr, v);
        if(qr > mid) mod(r(o), mid+1, r, ql, qr, v);

        push_up(o);
    }

    void update(int x, int v){update(1, 1, n, x, v);}
    ll query(int l, int r){return query(1, 1, n, l, r);}
    void mod(int l, int r, int v){mod(1, 1, n, l, r, v);}
}t;

int main(){
    n=read(), m=read();
    for(int i=1; i<=n; i++) t.update(i, read());
    while(m--){
        int type = read();
        if(type == 1){
            int l=read(), r=read();
            printf("%lld\n", t.query(l, r));
        }else if(type == 2){
            int l=read(), r=read(), v=read();
            t.mod(l, r, v);
        }else{
            int x=read(), v=read();
            t.update(x, v);
        }
    } 
    return 0;
}