Yura owns a quite ordinary and boring array 𝑎 of length 𝑛. You think there is nothing more boring than that, but Vladik doesn’t agree!
In order to make Yura’s array even more boring, Vladik makes 𝑞 boring queries. Each query consists of two integers 𝑥 and 𝑦. Before answering a query, the bounds 𝑙 and 𝑟 for this query are calculated: 𝑙=(𝑙𝑎𝑠𝑡+𝑥)mod𝑛+1, 𝑟=(𝑙𝑎𝑠𝑡+𝑦)mod𝑛+1, where 𝑙𝑎𝑠𝑡 is the answer on the previous query (zero initially), and mod is the remainder operation. Whenever 𝑙>𝑟, they are swapped.
After Vladik computes 𝑙 and 𝑟 for a query, he is to compute the least common multiple (LCM) on the segment [𝑙;𝑟] of the initial array 𝑎 modulo 109+7. LCM of a multiset of integers is the smallest positive integer that is divisible by all the elements of the multiset. The obtained LCM is the answer for this query.
Help Vladik and compute the answer for each query!
Input
The first line contains a single integer 𝑛 (1≤𝑛≤105) — the length of the array.
The second line contains 𝑛 integers 𝑎𝑖 (1≤𝑎𝑖≤2⋅105) — the elements of the array.
The third line contains a single integer 𝑞 (1≤𝑞≤105) — the number of queries.
The next 𝑞 lines contain two integers 𝑥 and 𝑦 each (1≤𝑥,𝑦≤𝑛) — the description of the corresponding query.
Output
Print 𝑞 integers — the answers for the queries.
Example
inputCopy
3
2 3 5
4
1 3
3 3
2 3
2 3
outputCopy
6
2
15
30
Note
Consider the example:
boundaries for first query are (0+1)mod3+1=2 and (0+3)mod3+1=1. LCM for segment [1,2] is equal to 6;
boundaries for second query are (6+3)mod3+1=1 and (6+3)mod3+1=1. LCM for segment [1,1] is equal to 2;
boundaries for third query are (2+2)mod3+1=2 and (2+3)mod3+1=3. LCM for segment [2,3] is equal to 15;
boundaries for fourth query are (15+2)mod3+1=3 and (15+3)mod3+1=1. LCM for segment [1,3] is equal to 30.
题意:
强制在线求区间lcm
思路:
如果不强制在线那么可以用莫队+unordered_map搞一搞,维护每个质因子的次幂。
考虑强制在线。因为小于 2 e 5 \sqrt{2e5} 2e5的质数只有88个,而每个数大于 2 e 5 \sqrt{2e5} 2e5的质数最多只有一个所以每个数只需要考虑88个小质因子+一个大质因子。
小质因子可以用线段树维护,线段树维护每个质因子最大次幂即可。
大质因子怎么办呢?实际就是维护区间出现的每种质因子的乘积,也就是不能算多次。这一步可以用主席树维护(明天早上补一下)
但是用线段树也可以维护,就是对于每个数的大质因子,保存其上一次出现的位置。
那么线段树维护出子区间的每个质因子上次出现的位置,用pair存{pre,v},再排序。
则询问区间
[
l
,
r
]
[l,r]
[l,r]时,只需要用二分找出上一次出现位置小于l的段即可,因为段是单调的,所以可以用前缀和维护合法的部分。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;
//素数筛
int v[maxn],p[maxn],cnt;
ll ans;
unordered_map<int,int>mp;
void getprime() {
for(int i = 2;i < maxn;i++) {
if(!v[i]) {
p[++cnt] = i;
v[i] = i;
}
for(int j = 1;j <= cnt && 1ll * p[j] * i < maxn;j++) {
v[i * p[j]] = p[j];
if(i % p[j] == 0) break;
}
}
}
//线段树
struct Tree {
int l,r;
int p[100]; //素因子
vector<pair<int,int> >vec;
vector<ll>sum;
}t[maxn << 2];
int a[maxn];
void pushup(int i) {
for(int j = 1;j <= 88;j++) {
t[i].p[j] = max(t[i * 2].p[j],t[i * 2 + 1].p[j]);
}
for(int j = 0;j < t[i * 2].vec.size();j++) {
t[i].vec.push_back(t[i * 2].vec[j]);
}
for(int j = 0;j < t[i * 2 + 1].vec.size();j++) {
t[i].vec.push_back(t[i * 2 + 1].vec[j]);
}
sort(t[i].vec.begin(),t[i].vec.end());
ll now = 1;
for(int j = 0;j < t[i].vec.size();j++) {
now = now * t[i].vec[j].second % mod;
t[i].sum.push_back(now);
}
}
void build(int i,int l,int r) {
t[i].l = l;t[i].r = r;
if(l == r) {
int num = a[l];
for(int j = 1;j <= 88;j++) { //线段树只维护前88个质数
while(num % p[j] == 0) {
t[i].p[j]++;
num /= p[j];
}
}
if(num != 1) {
if(mp[num]) {
t[i].vec.push_back({mp[num],num});
} else {
t[i].vec.push_back({0,num});
}
t[i].sum.push_back(num);
mp[num] = l;
}
return;
}
int m = (l + r) >> 1;
build(i * 2,l,m);
build(i * 2 + 1,m + 1,r);
pushup(i);
}
int res[100];
void query(int i,int l,int r) {
if(l <= t[i].l && t[i].r <= r) {
for(int j = 1;j <= 88;j++) {
res[j] = max(t[i].p[j],res[j]);
}
int pos = lower_bound(t[i].vec.begin(),t[i].vec.end(),make_pair(l,0)) - t[i].vec.begin();
if(pos) {
ans = ans * t[i].sum[pos - 1] % mod;
}
return;
}
int m = (t[i].l + t[i].r) >> 1;
if(l <= m) query(i * 2,l,r);
if(r > m) query(i * 2 + 1,l,r);
}
ll qpow(ll x,ll n) {
ll res = 1;
while(n) {
if(n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
int main() {
getprime();
int n;scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
build(1,1,n);
int q;scanf("%d",&q);
ans = 0;
for(int i = 1;i <= q;i++) {
int l,r;scanf("%d%d",&l,&r);
l = (l + ans) % n + 1;
r = (r + ans) % n + 1;
if(l > r) swap(l,r);
memset(res,0,sizeof(res));
ans = 1;
query(1,l,r);
for(int j = 1;j <= 88;j++) {
ans = ans * qpow(p[j],res[j]) % mod;
}
printf("%lld\n",ans);
}
return 0;
}
可持久化线段树:
因为每个数最多只有一个”大因子“,所以可以用主席树维护区间每种数的乘积。
方法是按照前缀建主席树,也就是
[
1
,
r
]
[1,r]
[1,r]的主席树建立在
[
1
,
r
−
1
]
[1,r-1]
[1,r−1]的基础上。
对于每个数的这个大因子
x
x
x,寻找上一次出现的位置
l
a
s
t
last
last,当前主席树的第
l
a
s
t
last
last位置更新为
i
n
v
(
x
)
inv(x)
inv(x)(代表逆元)。再将当前第
i
i
i个位置更新为
a
[
i
]
a[i]
a[i]。
主席树维护区间乘积即可。
询问
[
l
,
r
]
[l,r]
[l,r]区间,就是对于第
r
r
r棵主席树,询问
[
l
,
r
]
[l,r]
[l,r]区间。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;
int a[maxn],last[maxn];
int n,m;
ll ans;
//主席树开始
struct Tree {
int l,r;
int L,R;
ll v;
}t[maxn * 20];
int T[maxn],tot;
int build(int l,int r) {
int p = ++tot;
int mid = (l + r) >> 1;
t[p].L = l;
t[p].R = r;
if(l == r) return p;
t[p].l = build(l,mid);
t[p].r = build(mid + 1,r);
t[p].v = 0;
return p;
}
int update(int pre,int pos,ll v) {
int p = ++tot;
t[p] = t[pre];
t[p].v = max(t[pre].v,1ll) * v % mod;
if(t[p].L == t[p].R) return p;
int mid = (t[p].L + t[p].R) >> 1;
if(pos <= mid) {
t[p].l = update(t[pre].l,pos,v);
} else {
t[p].r = update(t[pre].r,pos,v);
}
return p;
}
ll query(int i,int l,int r) {
if(l <= t[i].L && t[i].R <= r) {
return max(t[i].v,1ll);
}
ll res = 1;
int mid = (t[i].L + t[i].R) >> 1;
if(l <= mid) res = res * query(t[i].l,l,r) % mod;
if(r > mid) res = res * query(t[i].r,l,r) % mod;
return res;
}
//主席树结束
ll qpow(ll x,ll n) {
ll res = 1;
while(n) {
if(n & 1) res = res * x % mod;
n >>= 1;
x = x * x % mod;
}
return res;
}
int v[maxn],p[maxn],cnt;
unordered_map<int,int>mp;
void getprime() {
for(int i = 2;i < maxn;i++) {
if(!v[i]) {
p[++cnt] = i;
v[i] = i;
}
for(int j = 1;j <= cnt && 1ll * p[j] * i < maxn;j++) {
v[i * p[j]] = p[j];
if(i % p[j] == 0) break;
}
}
}
//线段树
struct Tree2 {
int l,r;
int p[100]; //素因子
}t2[maxn << 2];
void pushup(int i) {
for(int j = 1;j <= 88;j++) {
t2[i].p[j] = max(t2[i * 2].p[j],t2[i * 2 + 1].p[j]);
}
}
void build2(int i,int l,int r) {
t2[i].l = l;t2[i].r = r;
if(l == r) {
for(int j = 1;j <= 88;j++) { //线段树只维护前88个质数
while(a[l] % p[j] == 0) {
t2[i].p[j]++;
a[l] /= p[j];
}
}
return;
}
int m = (l + r) >> 1;
build2(i * 2,l,m);
build2(i * 2 + 1,m + 1,r);
pushup(i);
}
int res[100];
void query2(int i,int l,int r) {
if(l <= t2[i].l && t2[i].r <= r) {
for(int j = 1;j <= 88;j++) {
res[j] = max(t2[i].p[j],res[j]);
}
return;
}
int m = (t2[i].l + t2[i].r) >> 1;
if(l <= m) query2(i * 2,l,r);
if(r > m) query2(i * 2 + 1,l,r);
}
//线段树结束
int main() {
getprime();
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
build2(1,1,n);
T[0] = build(1,n);
for(int i = 1;i <= n;i++) {
T[i] = T[i - 1];
if(a[i] == 1) continue;
if(last[a[i]]) {
T[i] = update(T[i],last[a[i]],qpow(a[i],mod - 2));
}
T[i] = update(T[i],i,a[i]);
last[a[i]] = i;
}
int q;scanf("%d",&q);
for(int i = 1;i <= q;i++) {
int l,r;scanf("%d%d",&l,&r);
l = (l + ans) % n + 1;
r = (r + ans) % n + 1;
if(l > r) swap(l,r);
memset(res,0,sizeof(res));
ans = 1;
query2(1,l,r);
for(int j = 1;j <= 88;j++) {
ans = ans * qpow(p[j],res[j]) % mod;
}
ans = ans * query(T[r],l,r) % mod;
printf("%lld\n",ans);
}
return 0;
}