跳转至

势能线段树

维护一个序列的过程中,如果所有的修改操作都只降低势能,即使暴力将区间展开为单点,有效操作的总次数也是有保证的,应为 \(\sum_{i=1}^n \Phi(a_i)\)

然而,\(\sum_{i=1}^{m}{r_i-l_i+1}\) 却可能达到 \(O(n^2)\) 级别。因此我们需要筛选出当前操作可能会影响到的位置,只对这些位置进行操作。

如果我们可以通过某种区间半群信息 \(w(l,r)\) 快速推断出当前操作是否会对区间 \([l,r]\) 产生影响,就可以使用线段树来维护。这里的线段树称为势能线段树。

具体的,当线段树递归到一个节点 \([l,r]\)

  • 通过区间信息 \(tr[p]\) 推断当前修改操作是否会对 \([l,r]\) 产生影响;若不会,则直接剪枝;
  • 递归修改左右儿子;

这样,每一个元素每被进行一次有效的操作,都会产生 \(O(\log n)\) 的时间复杂度。因此总时间复杂度不超过 \(O(\sum_{i=1}^n \Phi(a_i)\log n)\)

P4145 上帝造题的七分钟 2 / 花神游历各国

题意

你需要维护一个长为 \(n\) 的序列,实现两种操作:

  • 给定 \(l,r\),给区间 \([l,r]\) 内的每个数开平方根(向下取整);
  • 给定 \(l,r\),求 \([l,r]\) 的区间和;

\(n\le 10^5,\ a_i\le 10^12\)

注意到开平方根的操作可以使用势能分析。对一个数字 \(a_i\) 开平方根,最多开 \(1+\log \log a_i\) 次,就会把 \(a_i\) 变为 \(1\),以后的操作就都无效了。

当且仅当一个区间内的所有数字都等于 \(1\),开根操作对它无效。因此我们可以记录区间 \(\max\) 来判断是否剪枝。

时间复杂度 \(O(n\log n\log \log V)\)

代码
#include<iostream>
#include<cmath>
#define int long long
#define cint const int&
using namespace std;
const int N = 1E5 + 10;

int n, T;
int a[N];

namespace SegT {
    inline int lc(int x) { return x << 1; }
    inline int rc(int x) { return x << 1 | 1; }
    int mx[4 * N], sum[4 * N];
    inline void push_up(cint p) {
        mx[p] = max(mx[lc(p)], mx[rc(p)]);
        sum[p] = sum[lc(p)] + sum[rc(p)];
    }
    void build(int p, int l, int r) {
        if(l == r) {
            mx[p] = sum[p] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lc(p), l, mid);
        build(rc(p), mid + 1, r);
        push_up(p);
    }
    void modify(int p, int l, int r, int ql, int qr) {
        if(mx[p] <= 1) return; // 剪枝
        if(l == r) {
            mx[p] = sum[p] = sqrt(sum[p]);
            return;
        }
        int mid = (l + r) >> 1;
        if(mid >= ql) modify(lc(p), l, mid, ql, qr);
        if(mid < qr) modify(rc(p), mid + 1, r, ql, qr);
        push_up(p);
    }
    int query(int p, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr) {
            return sum[p];
        }
        int mid = (l + r) >> 1, res = 0;
        if(mid >= ql) res += query(lc(p), l, mid, ql, qr);
        if(mid < qr) res += query(rc(p), mid + 1, r, ql, qr);
        return res;
    }
}

signed main() {

    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    SegT::build(1, 1, n);

    cin >> T;
    while(T--) {
        int k, l, r;
        cin >> k >> l >> r;
        if(l > r) swap(l, r);
        if(k == 0) {
            SegT::modify(1, 1, n, l, r);
        } else {
            cout << SegT::query(1, 1, n, l, r) << '\n';
        }
    }

    return 0;
}

P9989 [Ynoi Easy Round 2023] TEST_69

题意

你需要维护一个长为 \(n\) 的序列,实现两种操作:

  • 给定 \(l,r,x\),将区间 \([l,r]\) 内的每个数 \(a_i\) 变为 \(\gcd(a_i,x)\)
  • 给定 \(l,r\),求 \([l,r]\) 的区间和;

\(n\le 2\times 10^5,\ m\le 5\times 10^5,\ 1\le a_i,x\le 10^{18}\)

一次有效的 \(\gcd\) 至少会让 \(a_i\) 减半。考虑如何判断操作是否会影响当前区间。当且仅当所有数字都是 \(x\) 的因数时,\(a_i\) 不变。此时,区间的 \(\operatorname{lcm}\) 一定也是 \(x\) 的因数。

热知识:\(\operatorname{lcm}_{i\mid x}\{i\}=x\)

因此我们维护区间 \(\operatorname{lcm}\)。如果 \(\operatorname{lcm}>10^{18}\),则肯定不是 \(x\) 的因数,直接标记为 INF。这里注意特判。

时间复杂度 \(O(n\log n\log V)\)

代码
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 2e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;

inline ll __lcm(ll a, ll b) {
    b /= __gcd(a, b);
    if((double)INF / b <= a) return INF;
    return a * b;
}

int n, m;
ll a[N];

namespace SegT {
    unsigned int sum[4 * N];
    ll lcm[4 * N];
    inline int lc(int x) { return x << 1; }
    inline int rc(int x) { return x << 1 | 1; }
    inline void push_up(int p) {
        sum[p] = sum[lc(p)] + sum[rc(p)];
        lcm[p] = __lcm(lcm[lc(p)], lcm[rc(p)]);
    }
    void build(int p, int l, int r) {
        if(l == r) {
            sum[p] = lcm[p] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lc(p), l, mid);
        build(rc(p), mid + 1, r);
        push_up(p);
    }
    void modify(int p, int l, int r, int ql, int qr, ll v) {
        if(l > qr || r < ql) return;
        if(v % lcm[p] == 0) return;
        if(l == r) {
            sum[p] = lcm[p] = __gcd(lcm[p], v);
            return;
        }
        int mid = (l + r) >> 1;
        if(ql <= mid) modify(lc(p), l, mid, ql, qr, v);
        if(mid < qr) modify(rc(p), mid + 1, r, ql, qr, v);
        push_up(p);
    }
    unsigned int query(int p, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr) {
            return sum[p];
        }
        int mid = (l + r) >> 1;
        if(qr <= mid) return query(lc(p), l, mid, ql, qr);
        if(mid < ql) return query(rc(p), mid + 1, r, ql, qr);
        return query(lc(p), l, mid, ql, qr) + query(rc(p), mid + 1, r, ql, qr);
    }
}

int main() {

    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];

    SegT::build(1, 1, n);

    while(m--) {
        int op, l, r;
        ll x;
        cin >> op;
        if(op == 1) {
            cin >> l >> r >> x;
            SegT::modify(1, 1, n, l, r, x);
        } else {
            cin >> l >> r;
            cout << SegT::query(1, 1, n, l, r) << '\n';
        }
    }

    return 0;
}