Codeforces 1422F. Boring Queries(线段树+二分、可持久化线段树)

该博客讨论了一种在线算法,用于处理在给定数组中计算区间最小公倍数(LCM)的查询。算法涉及素数筛、线段树和主席树的数据结构,以高效地处理不超过200000的数的质因数分解,并能应对在线查询的挑战。博客提供了两种解决方案,一种使用线段树维护小质因子,另一种利用主席树处理大质因子和区间乘积。
摘要由CSDN通过智能技术生成

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,r1]的基础上。
对于每个数的这个大因子 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;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值