[CF1422F]Boring Queries
壹、题目描述 ¶
贰、题解 ¶
下设 \(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\) 的部分往往可以直接暴力维护。