Codeforces Round 330 (Div. 1) D. REQ(线段树+离线)

原题链接:D. REQ


题目大意:


给出一个长度为 n n n 的正整数数组 a a a ,你要按顺序处理以下 q q q 个询问:

  • 每个操作给出 l l l r r r ,代表询问的区间 [ l , r ] [l,r] [l,r] ,你要回答出 ψ ( ∏ i = l r a i ) \psi(\prod_{i=l}^{r}a_{i}) ψ(i=lrai) 的值并对 1 0 9 + 7 10^{9}+7 109+7 取模后输出。

其中 ψ ( n ) \psi(n) ψ(n) 为欧拉函数,即 [ 1 , 2 , . . . , n ] [1,2,...,n] [1,2,...,n] 中与 n n n 互质的数的个数。

解题思路:


看起来非常线段树。

我们知道 ψ ( n ) \psi(n) ψ(n) 的值为对 n n n 进行质因数分解,得到 n = p 1 k 1 ⋅ p 2 k 2 ⋅ . . . ⋅ p m k m n=p_{1}^{k_{1}} \cdot p_{2}^{k_{2}} \cdot ... \cdot p_{m}^{k_{m}} n=p1k1p2k2...pmkm

那么 ψ ( n ) = ( p 1 − 1 ) ⋅ p 1 k 1 − 1 ⋅ ( p 2 − 1 ) ⋅ p 2 k 2 − 1 ⋅ . . . ⋅ ( p m − 1 ) ⋅   p m k m − 1 \psi(n)=(p_{1}-1) \cdot p_{1}^{k_{1}-1} \cdot(p_{2} - 1) \cdot p_{2}^{k_{2}-1} \cdot ... \cdot (p_{m}-1) \cdot \ p_{m}^{k_{m}-1} ψ(n)=(p11)p1k11(p21)p2k21...(pm1) pmkm1
(其中 p 1 , p 2 , . . . , p m p_{1},p_{2},...,p_{m} p1,p2,...,pm n n n 的质因子, k 1 , k 2 , . . . , k m k_{1},k_{2},...,k_{m} k1,k2,...,km 为分别为各个质因子的幂次)

看起来没有什么很好的做法,但注意到没有修改操作,我们可以把操作全都离线下来。

对每个操作按照 r r r 升序排序,同时记录一个 t t t 表示这是第几次的询问。

我们处理到 r r r 时,所有比 r r r 小的询问 [ l ′ , r ′ ] [l',r'] [l,r] 已经被处理过了,我们只需要考虑处理只包含 r r r 的询问即可。

先加入 a r a_{r} ar ,我们把 a r a_{r} ar 分解质因子,然后再考虑维护。

对于一个质因子 p i p_{i} pi,它作为答案时肯定是 ( p i − 1 ) ⋅ p i k i − 1 (p_{i} - 1) \cdot p_{i}^{k_{i}-1} (pi1)piki1 的形式。

而我们知道,我们现在所处理的询问是一定包含 a r a_{r} ar 的,所以我们要把上次出现 p i p_{i} pi 的位置从 ( p i ′ − 1 ) ⋅ p i ′ k i ′ − 1 (p_{i'} - 1) \cdot p_{i'}^{k_{i'}-1} (pi1)piki1 的形式修改成 p i ′ k i ′ p_{i'}^{k_{i'}} piki 的形式,然后再把 r r r 的位置修改成 ( p i − 1 ) ⋅ p i k i − 1 (p_{i} - 1) \cdot p_{i}^{k_{i}-1} (pi1)piki1 的形式,可知这样不会影响我们最终答案。

然后就是单点修改,区间查询的线段树维护一下区间积就好了。

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

(对 O ( log ⁡ V ) O(\log V) O(logV) 个质因子执行线段树修改操作,其中 V V V a i a_{i} ai 的值域)

AC代码:

#include <bits/stdc++.h>
using namespace std;

using PII = pair<int, int>;
using i64 = long long;

template<class Info>
struct Segtree {
#define lson k << 1, l, mid
#define rson k << 1 | 1, mid + 1, r
    int n;
    vector<Info> info;
    Segtree(int _n) : n(_n), info((_n + 5) << 2) {};
    Segtree(vector<Info>& arr) : Segtree(arr.size() - 1) {
        function<void(int, int, int)> build = [&](int k, int l, int r) {
            if (l == r) {
                info[k] = arr[l];
                return;
            }
            int mid = l + r >> 1;
            build(lson), build(rson);
            pushup(k);
        };
        build(1, 1, n);
    }
    void pushup(int k) {
        info[k] = merge(info[k << 1], info[k << 1 | 1]);
    }
    void Modify(int k, int l, int r, int x, const Info& z) {
        if (l == r) {
            info[k] = merge(info[k], z);
            return;
        };
        int mid = l + r >> 1;
        if (x <= mid) Modify(lson, x, z);
        if (x > mid) Modify(rson, x, z);
        pushup(k);
    }
    Info Query(int k, int l, int r, int x, int y) {
        if (l >= x && r <= y) return info[k];
        int mid = l + r >> 1;
        if (y <= mid) return Query(lson, x, y);
        if (x > mid) return Query(rson, x, y);
        return merge(Query(lson, x, y), Query(rson, x, y));
    }
    void Modify(int pos, const Info& z) {
        Modify(1, 1, n, pos, z);
    }
    Info Query(int l, int r) {
        return Query(1, 1, n, l, r);
    }
};

const int N = 1e6 + 1, mod = 1e9 + 7;

//Info内的 down 为父亲标记对子节点的下传 t为父亲下传的标记
//Info内的 merge 为父亲节点对儿子节点信息的合并 a为左儿子 b为右儿子

// sum 为区间积
struct Info {
    int sum;
    friend Info merge(const Info& a, const Info& b) {
        Info res;
        res.sum = 1LL * a.sum * b.sum % mod;
        return res;
    }
};

vector<int> prime;
int minp[N];

int qpow(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = 1LL * a * a % mod) {
        if (b & 1) {
            res = 1LL * res * a % mod;
        }
    }
    return res;
}

int inv(int a) {
    return qpow(a, mod - 2);
}

map<int, int> Inv;

void solve() {
    int n;
    cin >> n;

    vector<Info> A(n + 1);

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

    int q;
    cin >> q;
    vector<tuple<int, int, int>> ask(q);
    for (int i = 0; i < q; ++i) {
        auto& [l, r, t] = ask[i];
        cin >> l >> r;
        t = i;
    }

    sort(ask.begin(), ask.end(), [&](auto& A, auto& B) {
        auto& [l1, r1, t1] = A;
        auto& [l2, r2, t2] = B;
        return r1 < r2;
    });

	//随便开一个什么数组记录一下上一次质因子出现的位置
    unordered_map<int, int> last;

    int cur = 1;
    vector<int> ans(q);

    Segtree<Info> Seg(A);

    for (auto& [l, r, t] : ask) {
        while (cur <= r) {
            int val = A[cur].sum;
            while (val != 1) {
                int t = minp[val];
                while (val % t == 0) {
                    val /= t;
                }
                //找到上一个质因子的位置 直接乘上 (1 / (pi - 1)) * pi
                if (last[t]) {
                    Seg.Modify(last[t], { 1LL * inv(t - 1) * t % mod });
                }
                //把当前位置乘上 (1 / pi) * (pi - 1)
                Seg.Modify(last[t] = cur, { 1LL * inv(t) * (t - 1) % mod });
            }
            ++cur;
        }
        //将当前答案是 t 次的询问
        ans[t] = Seg.Query(l, r).sum;
    }

    for (auto& v : ans) {
        cout << v << "\n";
    }
}

signed main() {

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

    //与处理一下质数
    for (int i = 2; i < N; ++i) {
        if (!minp[i]) {
            prime.emplace_back(minp[i] = i);
        }
        for (auto& p : prime) {
            if (i * p >= N) break;
            minp[i * p] = p;
            if (i % p == 0) break;
        }
    }

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值