[CF1422F]Boring Queries

\[\text{newcommand 写的 LaTeX 起手式看不到就是了...} \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \]

壹、题目描述 ¶

传送门 to CF.

贰、题解 ¶

下设 \(M\) 为所有数的值域,由题,显然 \(M=200000\).

有一种分块做法,具体即对于 \(\le \sqrt M\) 的质数都开一棵线段树维护区间指数最大值,由于所有数剩下的部分,即 \(>\sqrt M\) 的部分只可能有一个因数,现在就转化为区间不同数的 \(\lcm\),使用主席树即可。

直接维护显然不行。把所有数分解质因数,查询 \(\operatorname{lcm}\) 就相当于查询区间 \(\max\). 对于质因子小于 \(\sqrt M\) 的,我们可以直接开 \(\mathcal O\left(\frac{\sqrt M}{\log\sqrt M}\right)\) 棵线段树维护所有 \(\sqrt M\) 以内的质数的区间 \(\max\). 对于质因子大于 \(\sqrt M\) 的,每个数最多有 \(1\) 个这样的质因子。

所以问题就变成查询区间里面互不相同的数的乘积。记录第 \(i\) 个数上次出现的位置 \(prev[i]\) 和这个数 \(p[i]\),对于一个查询 \([l,r]\),如果 \(prev[i]<l\)\(l\le i\le r\),那就说明 \(p[i]\)\(\operatorname{lcm}\) 是有贡献的,\(ans\leftarrow ans\times p[i]\).

把这个东西放到线段树上,具体方法是在线段树上每个叶子节点,记录每个数上次出现的位置 \(prev\) 和这个数 \(p\),形成一个\(\operatorname{pair}(prev[i],p[i])\)。然后 \(\rm pushup\) 合并的时候,归并左右儿子的 \(\rm pair\) 数组(保持 \(\rm pair\)\(\rm first\) 升序)。查询的时候,只需把 \([l,r]\) 拆分成线段树上的节点,再在节点上的 \(\rm pair\) 数组上二分找到 \(prev[i]<l\) 的位置,这个位置的前缀积就是子区间对 \(\rm lcm\) 的贡献(所以归并的时候还要维护前缀积),再把所有子区间的贡献相乘。

时间复杂度 \(\mathcal O(kn+kq\log n+q\log^2n)\),空间复杂度 \(\mathcal O(n\log n+kn)\). 其中,\(k=\frac{\sqrt M}{\log\sqrt M}\).

但是还有一种并不需要值域分块的做法,我们考虑直接使用主席树,维护出每个右端点的情况。

具体来说,我们维护所有质数的所有指数的出现维护,当然,越靠右越好,我们举其中一个数为例子,那么我们就要维护这样的东西:

这个图应该很直观了,询问的时候将主席树上符合条件的区间乘起来即可。

时间复杂度 \(\mathcal O(7n\log n)\) 或者说是 \(\mathcal O(n\log n\log\log n)\). 空间复杂度 \(\mathcal O(n\log^2a)\).

叁、参考代码 ¶

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <vector>
using namespace std;

// # define NDEBUG
# include <cassert>

namespace Elaina {

# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
# define mmset(a, b) memset(a, b, sizeof (a))
# define mmcpy(a, b) memcpy(a, b, sizeof (a))

typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;

template <class T> inline T fab(T x) { return x<0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
template <class T> inline T readin(T x) {
    x=0; int f=0; char c;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

template <class T> inline void writc(T x, char s='\n') {
    static int fwri_sta[1005], fwri_ed=0;
    if(x<0) putchar('-'), x=-x;
    do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
    while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
    putchar(s);
}

} using namespace Elaina;

const int maxn=100000;
const int maxa=2e5;
const int loga=20;
const int maxnde=maxn*loga*loga;
const int mod=1e9+7;

inline int qkpow(int a, int n) {
    int ret=1;
    for(; n>0; n>>=1, a=1ll*a*a%mod)
        if(n&1) ret=1ll*ret*a%mod;
    return ret;
}

int prime[maxa+5], factor[maxa+5], pcnt;
bool vis[maxa+5];
inline void sieve() {
    vis[1]=1;
    rep(i, 2, maxa) {
        if(!vis[i]) prime[++pcnt]=i, factor[i]=i;
        for(int j=1; j<=pcnt && i*prime[j]<=maxa; ++j) {
            vis[i*prime[j]]=1;
            factor[i*prime[j]]=prime[j];
            if(i%prime[j]==0) break;
        }
    }
}

namespace saya {

int mul[maxnde+5];
int ls[maxnde+5], rs[maxnde+5];
int ncnt;

# define mid ((l+r)>>1)
# define _lhs ls[i], l, mid
# define _rhs rs[i], mid+1, r

void build(int& i, int l, int r) {
    i=++ncnt, mul[i]=1;
    if(l==r) return;
    build(_lhs), build(_rhs);
}

void modify(int pos, int val, int pre, int& i, int l, int r) {
    i=++ncnt, ls[i]=ls[pre], rs[i]=rs[pre], mul[i]=mul[pre];
    mul[i]=1ll*mul[i]*val%mod;
    if(l==r) return;
    if(pos<=mid) modify(pos, val, ls[pre], _lhs);
    else modify(pos, val, rs[pre], _rhs);
}

int query(int L, int R, int i, int l, int r) {
    if(!i) return 1;
    if(L<=l && r<=R) return mul[i];
    int ret=1;
    if(L<=mid) ret=query(L, R, _lhs);
    if(mid<R) ret=1ll*ret*query(L, R, _rhs)%mod;
    return ret;
}

# undef mid
# undef _lhs
# undef _rhs

}

int a[maxn+5], n, q, tp;

inline void input() {
    n=readin(1); //, q=readin(1), tp=readin(1);
    rep(i, 1, n) a[i]=readin(1);
}

int rt[maxn+5];
int pre_pos[maxa+5];
vector<pii>v;
inline void build() {
    saya::build(rt[0], 1, n);
    rep(i, 1, n) {
        int now=a[i]; rt[i]=rt[i-1];
        while(now>1) {
            int p=factor[now], inv=qkpow(p, mod-2);
            int power=1; v.clear();
            while(now%p==0) {
                power*=p, now/=p;
                if(pre_pos[power]) v.push_back({pre_pos[power], inv});
                pre_pos[power]=i;
            }
            saya::modify(i, power, rt[i], rt[i], 1, n);
            int inv_prod=1;
            for(int j=0, siz=v.size(); j<siz; ++j) {
                inv_prod=1ll*inv_prod*v[j].se%mod;
                if(j==siz-1 || v[j].fi!=v[j+1].fi)
                    saya::modify(v[j].fi, inv_prod, rt[i], rt[i], 1, n), inv_prod=1;
            }
        }
    }
}

inline void solve() {
    ll lst=0; int l, r;
    q=readin(1);
    while(q--) {
        l=readin(1), r=readin(1);
        l=(lst+l)%n+1, r=(lst+r)%n+1;
        if(l>r) swap(l, r);
        lst=saya::query(l, r, rt[r], 1, n);
        writc(lst);
    }
}

signed main() {
    // freopen("lcm.in", "r", stdin);
    // freopen("lcm.out", "w", stdout);
    sieve();
    input();
    build();
    solve();
    return 0;
}

肆、关键之处 ¶

一类的区间问题,都可以使用这种对于每个右端点使用某种数据结构维护出 \([1,r]\) 的信息再进行处理,无论是区间本质不同子串,还是该题,他们都使用这一种思想,并且还有许多能够离线的问题也可以使用这种思想。好像区间 \(\rm mex\) 也可以这样做?当然,它也可以分块......

当然,对于这种涉及质因数分解的题,往 对于因数的值域分块 想也是一种套路,毕竟所有数,在 \(\sqrt M\) 之上的质因数最多只可能有一个,而 \(\le \sqrt M\) 的部分往往可以直接暴力维护。

posted @ 2021-08-31 10:04  Arextre  阅读(58)  评论(0编辑  收藏  举报