codeforces 438D The Child and Sequence(线段树:单点更新+区间取模+区间和)

题意:

一个n个数的序列。对它进行 3 种操作。
  1 l r:输入a[l,r]的和
  2 l r x:令[l,r]所有数对x取模
  3 k x:令a[k] = x

每到操作1时输出和。

(1 ≤ n, m ≤ 1e5).  (1 ≤ a[i] ≤ 1e9) 

分析:

看到区间更新,应该想到懒惰标记。但是用懒惰标记应该满足两个条件:

  1. 标记可以合并
  2. 可以快速更新区间信息。

但是这道题不满足条件2,也就是无法快速更新区间和。而逐个更新每个数取模,仿佛又太慢了。

正确解法基于一个重要的结论,a % b <= a/2,这就意味着,任何一个数loga[i]次取模以内就能变为0。同时还注意到,a % b = a (a < b)。这两点便可以极大的降低时间复杂度。

至此,便得到解题思路。单点更新和区间和并无特殊之处,而对于每个区间取模操作,如同遍历这个区间所有的数字一样,逐个取模更新。但是若某个区间的区间最大值小于mod,那么这个区间就无需有任何操作。这相当于一个剪枝。所以我们需要两个数组,一个记录区间和,一个记录区间最大值。总的时间复杂度为O(nlognloga[i])。

其实线段树的实质就是一个dfs搜索树的过程,只不过在每次搜索的时候都判断区间是否重合、是否相交、区间某值是否满足条件等等,这些过程的实质就是剪枝。时间复杂度就是靠这些剪枝降到logn的,否则就像遍历一棵树一样是O(n)。

代码

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
#define lson rt*2,l,(r+l)/2
#define rson rt*2+1,(l+r)/2+1,r
const int MAXN = 1e5 + 5;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
ll mx[MAXN << 2], sum[MAXN << 2];
int n, m;
void pushup(int rt) {
    mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void build(int rt, int l, int r) {
    if (l == r) {
        scanf("%lld", &mx[rt]);
        sum[rt] = mx[rt];
        return;
    }
    build(lson);
    build(rson);
    pushup(rt);
}
ll query(int L, int R, int rt, int l, int r) {
    if (L <= l && R >= r) {
        return sum[rt];
    }
    if (R <= (l + r) / 2)  return query(L, R, lson);
    else if (L > (l + r) / 2)    return query(L, R, rson);
    else    return query(L, R, lson) + query(L, R, rson);
}
void update(int L, int R, int rt, int l, int r, int mod) {
    if (L <= l && R >= r) {
        if (mx[rt] < mod)    return;
        if (l == r) {
            mx[rt] = sum[rt] = mx[rt] % mod;
            return;
        }
    }
    if (L <= (l + r) / 2) update(L, R, lson, mod);
    if (R > (l + r) / 2) update(L, R, rson, mod);
    pushup(rt);
}
void change(int rt, int l, int r, int k, int x) {
    if (l == r) {
        mx[rt] = x;
        sum[rt] = x;
        return;
    }
    if (k <= (l + r) / 2)  change(lson, k, x);
    else    change(rson, k, x);
    pushup(rt);
}
int main() {
    while (~scanf("%d%d", &n, &m)) {
        build(1, 1, n);
        while (m--) {
            int op, L, R, k, x;
            scanf("%d", &op);
            if (op == 1) {
                scanf("%d%d", &L, &R);
                printf("%lld\n", query(L, R, 1, 1, n));
            } else if (op == 2) {
                scanf("%d%d%d", &L, &R, &x);
                update(L, R, 1, 1, n, x);
            } else {
                scanf("%d%d", &k, &x);
                change(1, 1, n, k, x);
            }
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值