【题解】[COCI2017-2018#2] Garaža
考点:分治思想 + 线段树。 (区间问题常见思考方式
考虑区间答案 = 左区间答案 + 右区间答案 + 跨区间答案。
注意到每次加入一个数时 gcd 要么不变,要么缩小到原来的 1/2
所以本质上只用 log a_i 个不同取值
这样双指针扫描就可以通过本题。
#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define ll long long
using namespace std;
const int Maxn = 1e5 + 5;
int n, q, a[Maxn];
inline int read() {
    int X = 0;
    bool flag = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            flag = 0;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        X = (X << 1) + (X << 3) + ch - '0';
        ch = getchar();
    }
    if (flag)
        return X;
    return ~(X - 1);
}
struct node {
    //(place, val) -> place 指的是区间中的相对位置
    //定义 pr 为从左到右 gcd 的答案
    // su 为从右到左 gcd 的答案
    // step 1. 合并两个区间答案 (双指针扫一遍)
    // step 2. 求出新的 pr 和 su 数组 (把 gcd 相同的区间合并在一起)
    //注意 gcd =1 的段也要算进去
    vector<pii> pr, su;
    ll s;
    node() {
        pr.clear();
        su.clear();
        s = 0;
    }
} t[Maxn << 2];
int gcd(int x, int y) { return y == 0 ? x : gcd(y, x % y); }
inline vector<pii> hb(vector<pii> x, vector<pii> y, int tx) {
    if (x.empty()) {
        return y;
    }
    if (y.empty()) {
        return x;
    }
    //为何保证正序合并,这里要先翻转回来
    if (tx) {
        reverse(x.begin(), x.end());
        reverse(y.begin(), y.end());
    }
    vector<pii> now;
    assert(now.size() == 0);
    int szx = x.size() - 1, szy = y.size() - 1;
    //前半部分直接粘贴
    for (int i = 0; i <= szx; i++) {
        now.push_back(x[i]);
    }
    //如果和前一项合并没有变化
    for (int i = 0; i <= szy; i++) {
        int sz = now.size() - 1;
        if (y[i].se % now[sz].se == 0) {
            now[sz].fi = y[i].fi + x[szx].fi;
        } else {
            now.push_back(make_pair(y[i].fi + x[szx].fi, gcd(y[i].se, now[sz].se)));
        }
    }
    if (tx) {
        reverse(now.begin(), now.end());
    }
    return now;
}
inline node meg(node x, node y) {
    //关键代码
    node now;
    now.s = x.s + y.s;
    now.pr = hb(x.pr, y.pr, 0);
    //这里 su 实际上是反着存储的
    //这样 hb 的时候就可以直接正序合并
    now.su = hb(y.su, x.su, 1);
    int szx = x.su.size() - 1, szy = y.pr.size() - 1;
    if (szx < 0 || szy < 0)
        return now;
    //双指针扫描
    int j = 0;
    for (int i = 0; i <= szx; i++) {
        while (j <= szy && gcd(x.su[i].se, y.pr[j].se) > 1) {
            j++;
        }
        //夹逼出一个范围
        int t = x.su[i].fi;
        if (i < szx) {
            t -= x.su[i + 1].fi;
        }
        if (j) {
            now.s += 1ll * t * y.pr[j - 1].fi;
        }
    }
    return now;
}
void fz(node &x, int y) {
	x.pr.clear(); x.su.clear();
    x.pr.push_back(make_pair(1, y));
    x.su.push_back(make_pair(1, y));
    x.s = (y > 1);
}
void build(int p, int l, int r) {
    if (l == r) {
        fz(t[p], a[l]);
        return;
    }
    int mid = l + r >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    t[p] = meg(t[p << 1], t[p << 1 | 1]);
}
void upd(int p, int l, int r, int x) {
    if (l == r) {
        fz(t[p], a[l]);
        return;
    }
    int mid = l + r >> 1;
    x <= mid ? upd(p << 1, l, mid, x) : upd(p << 1 | 1, mid + 1, r, x);
    t[p] = meg(t[p << 1], t[p << 1 | 1]);
}
inline node qry(int p, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) {
        return t[p];
    }
    int mid = l + r >> 1;
    if (ql <= mid && mid < qr) {
        return meg(qry(p << 1, l, mid, ql, qr), qry(p << 1 | 1, mid + 1, r, ql, qr));
    } else if (qr <= mid) {
        return qry(p << 1, l, mid, ql, qr);
    } else {
        return qry(p << 1 | 1, mid + 1, r, ql, qr);
    }
}
int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    build(1, 1, n);
    for (int i = 1; i <= q; i++) {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) {
            a[l] = r;
            upd(1, 1, n, l);
        } else {
            printf("%lld\n", qry(1, 1, n, l, r).s);
        }
    }
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号