LibreOJ - 6029 市场

题意:

给定一个长度为 n n n 的数组 a a a,有 q q q 次操作,① 1 , l , r , c 1, l, r, c 1,l,r,c,令 a i ′ = a i + c   ( i = l , l + 1 , ⋯   , r ) a_i' = a_i + c~(i = l, l + 1, \cdots, r) ai=ai+c (i=l,l+1,,r);② 2 , l , r , d 2, l, r, d 2,l,r,d,令 a i ′ = ⌊ a i d ⌋ a_i' = \lfloor\cfrac{a_i}{d}\rfloor ai=dai;③ 3 , l , r 3, l, r 3,l,r,求 min ⁡ i = l r {   a i   } \min\limits_{i = l}^{r}\{~a_i~\} i=lminr{ ai };④ 4 , l , r 4, l, r 4,l,r,求 ∑ i = l r a i \sum\limits_{i = l}^{r} a_i i=lrai ( n , q ≤ 1 0 5 , − 1 0 4 ≤ c ≤ 1 0 4 , 2 ≤ d ≤ 1 0 9 ) (n, q \leq 10^5, -10^4 \leq c \leq 10^4, 2 \leq d \leq 10^9) (n,q105,104c104,2d109)

链接:

https://vjudge.net/problem/LibreOJ-6029

解题思路:

考虑线段树。区间除法操作无法直接更新,注意到除法操作每次至少让区间所有数减小一半,如果仍简单考虑所有叶结点的势能,由于有区间加法操作,一次区间加至多重置所有叶结点的势能,复杂度不行。

进一步考虑,我们能直接更新并不仅当深入到叶结点,比如区间所有数相等,那么可以化为一次区间减法,注意到进行有限次区间除法后,整个区间可以变成同一个数,大约也是 O ( l o g a ) O(loga) O(loga) 次,考虑以此作为结点势能,即结点 [ l , r ] [l, r] [l,r] 势能为 l o g ( max ⁡ { a i } ) − l o g ( min ⁡ { a i } ) log(\max\{a_i\}) - log(\min\{a_i\}) log(max{ai})log(min{ai}),那么初始总势能为 O ( n l o g n l o g a ) O(nlognloga) O(nlognloga)。对于一次区间除法,第一步先定位到 O ( l o g n ) O(logn) O(logn) 个区间,复杂度 O ( l o g n ) O(logn) O(logn);第二步,若当前结点势能非 0 0 0,即最值差不为 0 0 0,那么继续深入子树,每向下访问一个结点,回溯时必然让该结点势能减少至少 1 1 1,即该阶段访问的结点总次数与总势能同阶,复杂度 O ( n l o g n l o g a ) O(nlognloga) O(nlognloga)

再考虑区间加法对总势能的影响,仍先定位到 O ( l o g n ) O(logn) O(logn) 个区间,此时路径上的 O ( l o g n ) O(logn) O(logn) 个结点势能至多增加 O ( l o g n l o g a ) O(lognloga) O(lognloga) (即 p u s h U p pushUp pushUp 时被更新的结点)。对于进行区间加法的区间,注意到区间最值并不改变,那么结点势能也不变。故区间加法对总势能的影响为 O ( l o g n l o g a ) O(lognloga) O(lognloga)

 
但是这并不完全对!我们注意到势能应该定义为被暴力修改的次数,但极差相同并不意味着被暴力修改的次数相同,考虑这样一组数 2 n − 1 , 2 n 2^n - 1, 2^n 2n1,2n,每次除以 2 2 2,差值多次维持在 1 1 1,最后才变为 0 0 0,这意味一次区间加法能使总势能爆炸,上述复杂度不成立。我们计算除法操作对区间极差的影响,记 y 1 = ⌊ x 1 d ⌋ , y 2 = ⌊ x 2 d ⌋ y_1 = \lfloor\cfrac{x_1}{d}\rfloor, y_2 = \lfloor\cfrac{x_2}{d}\rfloor y1=dx1,y2=dx2,其中 x 1 , x 2 , y 1 , y 2 x_1, x_2, y_1, y_2 x1,x2,y1,y2 分别为除法操作前后区间的区间最值, x 1 ≥ x 2 , y 1 ≥ y 2 x_1 \geq x_2, y_1 \geq y_2 x1x2,y1y2。改写 x 1 = y 1 ∗ d + r 1 , x 2 = y 2 ∗ d + r 2 x_1 = y_1 *d + r_1, x_2 = y_2 * d + r_2 x1=y1d+r1,x2=y2d+r2,其中 0 ≤ r 1 , r 2 < d 0 \leq r_1, r_2 \lt d 0r1,r2<d,作差得:
x 1 − x 2 = ( y 1 − y 2 ) ∗ d + ( r 1 − r 2 ) x_1 - x_2= (y_1 - y_2) * d + (r_1 - r_2) x1x2=(y1y2)d+(r1r2)
x 1 − x 2 = x , y 1 − y 2 = y , r 1 − r 2 = r x_1 - x_2 = x, y_1 - y_2 = y, r_1 - r_2 = r x1x2=x,y1y2=y,r1r2=r,其中 − d < r < d -d \lt r \lt d d<r<d
{ x − y ∗ d = r > − d   ⇒   y < ⌈ x d ⌉ + 1   ⇒   y ≤ ⌈ x d ⌉ x − y ∗ d = r < d   ⇒   y > ⌊ x d ⌋ − 1   ⇒   y ≥ ⌊ x d ⌋ \begin{cases} x - y * d = r \gt -d ~\Rightarrow~ y \lt \lceil\cfrac{x}{d}\rceil + 1 ~\Rightarrow~ y \leq \lceil\cfrac{x}{d}\rceil\\ x - y * d = r \lt d ~ \Rightarrow ~ y \gt \lfloor\cfrac{x}{d}\rfloor - 1 ~ \Rightarrow ~ y \geq \lfloor\cfrac{x}{d}\rfloor \end{cases} xyd=r>d  y<dx+1  ydxxyd=r<d  y>dx1  ydx
当且仅当 x > 1 x \gt 1 x>1 时, y y y 每次才会严格减少,而当 x = 1 x = 1 x=1,会在若干次除法操作使得 y = 1 y = 1 y=1 后最终使得 y = 0 y = 0 y=0,而 x = y x = y x=y 时是可以直接化除法为减法的。那么我们改变区间直接更新的条件,当 x = y x = y x=y 时我们直接进行更新,则相同的极差意味着相同的势能,在进行区间加法的时候,对于区间及其子区间的代表结点极差均不变,总势能也不变。总时间复杂度为 O ( m l o g n + n l o g n l o g a ) O(mlogn + nlognloga) O(mlogn+nlognloga)

参考代码:

#include
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e5 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int a[maxn];
int n, q;

struct SegTree{

    ll sum[maxn << 2], mx[maxn << 2], mn[maxn << 2], add[maxn << 2];
    void pushUp(int rt){

        sum[rt] = sum[lson] + sum[rson];
        mx[rt] = max(mx[lson], mx[rson]);
        mn[rt] = min(mn[lson], mn[rson]);
    }
    void build(int l, int r, int rt){

        add[rt] = 0;
        if(l == r){

            sum[rt] = mx[rt] = mn[rt] = a[l];
            return;
        }
        int mid = gmid;
        build(l, mid, lson);
        build(mid + 1, r, rson);
        pushUp(rt);
    }
    void pushDown2(int rt, int son, int len){

        if(add[rt]){

            add[son] += add[rt];
            sum[son] += len * add[rt];
            mx[son] += add[rt], mn[son] += add[rt];
        }
    }
    void pushDown(int l, int r, int rt){

        int mid = gmid;
        pushDown2(rt, lson, mid - l + 1);
        pushDown2(rt, rson, r - mid);
        add[rt] = 0;
    }
    void updateAdd(int l, int r, int rt, int L, int R, int val){

        if(l >= L && r <= R){

            add[0] = val;
            pushDown2(0, rt, r - l + 1);
            return;
        }
        int mid = gmid;
        pushDown(l, r, rt);
        if(L <= mid) updateAdd(l, mid, lson, L, R, val);
        if(R > mid) updateAdd(mid + 1, r, rson, L, R, val);
        pushUp(rt);
    }
    ll cal(ll x, ll y){

        if(x < 0) return (x + 1) / y - 1;
        else return x / y;
    }
    void updateDiv(int l, int r, int rt, int L, int R, int val){

        if(l >= L && r <= R && mx[rt] - mn[rt] == cal(mx[rt], val) - cal(mn[rt], val)){

            add[0] = cal(mx[rt], val) - mx[rt];
            pushDown2(0, rt, r - l + 1);
            return;
        }
        int mid = gmid;
        pushDown(l, r, rt);
        if(L <= mid) updateDiv(l, mid, lson, L, R, val);
        if(R > mid) updateDiv(mid + 1, r, rson, L, R, val);
        pushUp(rt);
    }
    ll queryMin(int l, int r, int rt, int L, int R){

        if(l >= L && r <= R) return mn[rt];
        int mid = gmid; ll ret = 1ll << 60;
        pushDown(l, r, rt);
        if(L <= mid) ret = min(ret, queryMin(l, mid, lson, L, R));
        if(R > mid) ret = min(ret, queryMin(mid + 1, r, rson, L, R));
        return ret;
    }
    ll querySum(int l, int r, int rt, int L, int R){

        if(l >= L && r <= R) return sum[rt];
        int mid = gmid; ll ret = 0;
        pushDown(l, r, rt);
        if(L <= mid) ret += querySum(l, mid, lson, L, R);
        if(R > mid) ret += querySum(mid + 1, r, rson, L, R);
        return ret;
    }
} tr;

int main(){

    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i <= n; ++i){

        cin >> a[i];
    }
    tr.build(1, n, 1);
    while(q--){

        int opt, x, y, z; cin >> opt >> x >> y; ++x, ++y;
        if(opt == 1){

            cin >> z;
            tr.updateAdd(1, n, 1, x, y, z);
        }
        else if(opt == 2){

            cin >> z;
            tr.updateDiv(1, n, 1, x, y, z);
        }
        else if(opt == 3){

            ll ret = tr.queryMin(1, n, 1, x, y);
            cout << ret << "\n";
        }
        else{

            ll ret = tr.querySum(1, n, 1, x, y);
            cout << ret << "\n";
        }
    }
    return 0;;
}

你可能感兴趣的:(数据结构,#,线段树)