天天看點

30.Boring Queries 可持久化權值線段樹維護區間GCD/LCM

30.Boring Queries 可持久化權值線段樹維護區間GCD/LCM

個人Limitの線段樹題單題解主目錄:Limitの線段樹題單 題解目錄_HeartFireY的部落格

給定序列,要求不帶修支援線上查詢區間最小公倍數對1e9+7取模的結果

主席樹不帶修線上維護區間GCD,記錄質因數最後一次出現的位置并消除貢獻

洛谷傳送門:​​CF1422F Boring Queries - 洛谷 | 計算機科學教育新生态 (luogu.com.cn)​​

CF傳送門:​​F. Boring Queries (codeforces.com)​​

題目分析

給定序列,要求支援線上查詢區間最小公倍數對取模的結果,不帶修。

考慮暴力求解的方式:直接合并最小公倍數,但是發現帶取模,無法合并。

但是發現最大公倍數可以轉化為求區間乘積除區間最大公因數。那麼可以想到用區間合并的方式求解,因為最小公倍數時可以兩兩區間進行合并求出的。但是帶取模,我們會發現由于出現了帶模意義下的除法,不能簡單的進行合并。

首先我們寫出區間最小公倍數的表達式:

再轉化為模意義下的表達式:

我們都知道,對于,我們可以對分解質因數,然後求公有因數連乘積,注意共有因數是有重複的。

我們可以将區間公有質因數表示為數對,表示質因數,表示在共有質因數中該因數的出現次數。那麼将共有質因數寫作集合,那麼可以将區間最大公因數寫作:

現在考慮加入一個數字的影響,假設現在有一個長度為的序列,設:

那麼對于單點,答案就是,但是對于區間,我們可以發現要除(最小公倍數式子)。那麼考慮如何消除多乘的貢獻。一個很妙的思路是,固定右端點進行枚舉,我們維護質因子的次幂在左邊最後一次出現的位置,每次插入單點時,邊分解質因數,邊對先前重疊的貢獻(即為區間的組成部分)進行消除。

我們分析上述序列的區間的計算過程,為了友善解釋,設:

  • 單點插入葉節點,,号葉子節點上有的貢獻,故
  • 不是質數,質因數分解,第一次分解到達,發現之前沒有重複貢獻,不需要消除
  • 變為仍不是質數,質因數分解,第二次分解到達,發現之前有重複貢獻,于是對号葉子節點乘消除貢獻
  • 單點插入節點到号葉子節點上,号葉子節點上疊加的貢獻

我們可以發現,最終答案就是各葉子節點的累乘。即我們通過對區間分解進行消除達到了消除重複貢獻的效果。

類似的,我們把和交換一下,分析插入順序,發現單個造成的重複貢獻在插入時仍會被消除。

但是我們發現,這樣沒法查詢任意區間。因為消除貢獻的計算會導緻葉節點發生變化。但是我們發現有一個東西很符合我們想要的效果:固定右端點查詢區間,每加入個右端點就會構成新序列,但與先前的序列有較大重疊。那麼顯然我們可以用可持久化權值線段樹維護這個東西。

Code

#include <bits/stdc++.h>
#pragma gcc optimize("O2")
#pragma g++ optimize("O2")
// #define int long long
#define endl '\n'
using namespace std;

const int N = 1e5 + 10, MOD = 1e9 + 7;

namespace ffastIO {
    const int bufl = 1 << 15;
    char buf[bufl], *s = buf, *t = buf;
    inline int fetch() {
        if (s == t) { t = (s = buf) + fread(buf, 1, bufl, stdin); if (s == t) return EOF; }
        return *s++;
    }
    // inline int read() {
    //  int a = 0, b = 1, c = fetch();
    //  while (!isdigit(c))b ^= c == '-', c = fetch();
    //  while (isdigit(c)) a = a * 10 + c - 48, c = fetch();
    //  return b ? a : -a;
    // }
    inline int read(){
        int f = 1, x = 0; char s = getchar(); 
        while(s < '0'||s > '9'){ if(s == '-') f = -1; s = getchar(); } 
        while(s >= '0' && s <= '9'){ x = x * 10 + s - '0'; s = getchar();}
        return x *= f; 
    }
}

using ffastIO::read;

int a[N], n, lastans;
int inv[N << 1], fac[N << 1], root[N], lst[N << 1];

inline int get(int x) { return (x + lastans) % n + 1; }

namespace SegTree{
    int tree[N * 400], lc[N * 400], rc[N * 400], tot;

    inline void push_up(int rt){ tree[rt] = 1ll * tree[lc[rt]] * tree[rc[rt]] % MOD; }

    void update(int &rt, int pre, int l, int r, int x, int num){
        rt = ++tot, lc[rt] = lc[pre], rc[rt] = rc[pre], tree[rt] = 1ll * tree[pre] * num % MOD;
        if(l == r) return;
        int mid = l + r >> 1;
        if(mid >= x) update(lc[rt], lc[pre], l, mid, x, num);
        else update(rc[rt], rc[pre], mid + 1, r, x, num);
        push_up(rt);
    }

    int query(int rt, int l, int r, int L, int R){
        if(l >= L && r <= R) return tree[rt];
        int mid = l + r >> 1; long long ans = 1;
        if(mid >= L) ans = (1ll * ans * query(lc[rt], l, mid, L, R)) % MOD;
        if(mid < R) ans = (1ll * ans * query(rc[rt], mid + 1, r, L, R)) % MOD;
        return ans;
    }
}


inline void solve(){
    n = read(), lastans = 0;
    for(int i = 1; i <= n; i++) a[i] = read();
    for(int i = 1; i <= n; i++){
        root[i] = root[i - 1];
        int now = a[i];
        while(fac[now]){
            int k = fac[now], t = 1;
            while(!(now % k)){
                t *= k, now /= k;
                if(lst[t]) SegTree::update(root[i], root[i], 1, n, lst[t], inv[k]);
                lst[t] = i;
            }
            SegTree::update(root[i], root[i], 1, n, i, t);
        }
    }
    int q = read();
    while(q--){
        int l = get(read()), r = get(read());
        if(l > r) swap(l, r);
        lastans = SegTree::query(root[r], 1, n, l, r);
        // cout << l << ' ' << r << endl;
        cout << lastans << endl;
    }
}

void init(){
    inv[1] = 1, SegTree::tree[0] = 1;
    for(int i = 2; i < 2 * N - 5; i++){
        inv[i] = 1ll * inv[MOD % i] * (MOD - MOD / i) % MOD;
        if(!fac[i]){ for(int j = i; j < 2 * N - 5; j += i) fac[j] = i; }
    }
}

signed main(){
    init();
    solve();
    return 0;
}