原题链接: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=p1k1⋅p2k2⋅...⋅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)=(p1−1)⋅p1k1−1⋅(p2−1)⋅p2k2−1⋅...⋅(pm−1)⋅ pmkm−1 。
(其中
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} (pi−1)⋅piki−1 的形式。
而我们知道,我们现在所处理的询问是一定包含 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} (pi′−1)⋅pi′ki′−1 的形式修改成 p i ′ k i ′ p_{i'}^{k_{i'}} pi′ki′ 的形式,然后再把 r r r 的位置修改成 ( p i − 1 ) ⋅ p i k i − 1 (p_{i} - 1) \cdot p_{i}^{k_{i}-1} (pi−1)⋅piki−1 的形式,可知这样不会影响我们最终答案。
然后就是单点修改,区间查询的线段树维护一下区间积就好了。
时间复杂度: 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;
}