Codeforces Round #675 (Div. 2) F. Boring Queries 区间lcm + 主席树

传送门

文章目录

题意:

给你一个长度为 n n n的序列 a a a q q q个询问,每次询问 [ l , r ] [l,r] [l,r]内的 l c m lcm lcm是多少,对 1 e 9 + 7 1e9+7 1e9+7取模。

n ≤ 1 e 5 , a ≤ 2 e 5 , q ≤ 1 e 5 n\le1e5,a\le2e5,q\le1e5 n1e5,a2e5,q1e5

思路:

关于 l c m lcm lcm,对于两个数的 l c m lcm lcm,为了防止爆炸我们通常写成 a / g c d ( a , b ) ∗ b a/gcd(a,b)*b a/gcd(a,b)b,如果求 1 − n 1-n 1n所有数的 l c m lcm lcm并且要求取模,而且 a , b a,b a,b都比较大,我们就不能这么求了,考虑将其分解为质因子的形式。

考虑 a ∗ b / g c d ( a , b ) a*b/gcd(a,b) ab/gcd(a,b)到底干了个什么事情,其实他将 a , b a,b a,b的每个质因子的幂次都取了 m a x max max之后就得到了他们的 l c m lcm lcm,换句话说,原本 a = p 1 x 1 p 2 x 2 . . . p n x n a=p_1^{x1}p_2^{x2}...p_n^{xn} a=p1x1p2x2...pnxn b = p 1 y 1 p 2 y 2 . . . p n y n b=p_1^{y1}p_2^{y2}...p_n^{yn} b=p1y1p2y2...pnyn,那么 l c m ( a , b ) = p 1 m a x ( x 1 , y 1 ) p 2 m a x ( x 2 , y 2 ) . . . p n m a x ( x n , y n ) lcm(a,b)=p_1^{max(x1,y1)}p_2^{max(x2,y2)}...p_n^{max(x_n,y_n)} lcm(a,b)=p1max(x1,y1)p2max(x2,y2)...pnmax(xn,yn),因为 g c d ( a , b ) gcd(a,b) gcd(a,b)是取了 m i n min min,除去之后就剩 m a x max max啦。

所以我们可以通过维护质因子的个数,最后就快速幂一下就好。

如果带修的话,需要挂到线段树上,考虑值域 a i ≤ 2 e 5 a_i\le 2e5 ai2e5的情况,我们可以对于 ≤ 500 \le 500 500的质数每个都建一颗线段树,最多也就 90 90 90个左右,让后对于其他的素数,幂次一定都是 1 1 1,我们查询区间内不同的数的乘积,用主席树维护一下即可,对于 90 90 90个线段树,我们只需要维护区间 m a x max max即可。

还可以将 90 90 90棵线段树换成 s t st st表,让后用 c h a r char char存,这样能快点。

这个的复杂度 O ( n ∗ 90 ∗ l o g n ) O(n*90*logn) O(n90logn),常数有点大,虽然卡卡能过,但是显然不优,我们考虑更优解法。

考虑我们离线怎么做,显然我们套路的将其按照右端点排序,现在我们就固定了右端点,左端点在 l l l,由于 l c m lcm lcm是单调不减的,所以考虑一个单调性,我们将其移动到 l − 1 l-1 l1的话贡献是多少呢?假设 a l − 1 a_{l-1} al1有一个质因子 p p p,其幂次为 x x x,在 [ l , r ] [l,r] [l,r]之间的 p p p幂次为 y y y,假设此时 x > y x>y x>y,那么显然他对 l c m lcm lcm的贡献为 p x − y p^{x-y} pxy,否则其没有贡献。

这启发了我们什么呢?我们是否可以像线段树离线维护数出现的最后位置一样,维护质因子出现的最后位置呢?假设现在有两个位置 i < j i<j i<j,质因子 p i x , p j y p_i^{x},p_j^{y} pix,pjy,假设 x < y x<y x<y,那么将 i i i位置的质因子都删去,在 j j j位置乘上 p y p^y py显然是最优的,这样来看直接取最后的位置是没问题的,但是如果 x > y x>y x>y,这个时候按照上面说的我们就不会更新,但是如果后面查询 [ j , p o s ] [j,pos] [j,pos]的时候,由于我们没加上 j j j位置质因子的贡献,所以会导致答案错误。

所以我们考虑维护一下每个质因子的每个幂次出现的位置,这样对于 x > y x>y x>y的情况,相当于将 i i i位置除上个 p y p_y py,变成 p x − y p^{x-y} pxy,让后将 j j j位置更新为 p y p^y py,这样查询 [ j , p o s ] [j,pos] [j,pos]的时候不会丢掉 j j j的贡献,并且查询 [ x , y ] [x,y] [x,y]的时候,由于维护的是乘积,即 p x − y ∗ p y = p x p^{x-y}*p^{y}=p^x pxypy=px,也不会丢掉 i i i位置的最大值,这样这个问题就完美解决了。

复杂度由 a a a来确定,由于每个数最多有不超过 l o g a loga loga个质因子,所以时间复杂度上界是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),空间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

// Problem: F. Boring Queries
// Contest: Codeforces - Codeforces Round #675 (Div. 2)
// URL: https://codeforces.com/contest/1422/problem/F
// Memory Limit: 512 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)
 
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#include<random>
#include<cassert>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid ((tr[u].l+tr[u].r)>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;
 
//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }
 
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
 
const int N=200010,mod=1e9+7,INF=0x3f3f3f3f;
const double eps=1e-6;
 
int n,m;
int a[N],pre[N];
int root[N],idx;
struct Node {
	int l,r;
	LL mul;
}tr[N*100];
int prime[2*N+10],cnt,ne[2*N];
bool st[2*N+10];
 
void get_prime(int n) 
{
    for(int i=2;i<=n;i++)
    {
        if(!st[i]) prime[cnt++]=i,ne[i]=i;
        for(int j=0;prime[j]<=n/i;j++)
        {
            st[prime[j]*i]=true;
            ne[prime[j]*i]=prime[j];
            if(i%prime[j]==0) break;    
        } 
    }
} 
 
LL qmi(LL a,LL b) {
	LL ans=1;
	while(b) {
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans%mod;
}
 
void build(int &u,int l,int r) {
	u=++idx; tr[u].mul=1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(tr[u].l,l,mid); build(tr[u].r,mid+1,r);
}
 
void insert(int p,int &q,int l,int r,int pos,int val) {
	q=++idx; tr[q]=tr[p];
	tr[q].mul*=val; tr[q].mul%=mod;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(pos<=mid) insert(tr[p].l,tr[q].l,l,mid,pos,val);
	else insert(tr[p].r,tr[q].r,mid+1,r,pos,val);
}
 
LL query(int u,int l,int r,int ql,int qr) {
	if(!u) return 1;
	if(l>=ql&&r<=qr) return tr[u].mul;
	LL ans=1,mid=(l+r)>>1;
	if(ql<=mid) ans=ans*query(tr[u].l,l,mid,ql,qr)%mod;
	if(qr>mid) ans=ans*query(tr[u].r,mid+1,r,ql,qr)%mod;
	return ans;
}
 
int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);
	
	get_prime(3e5);
	scanf("%d",&n);
	build(root[0],1,n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) {
		int now=a[i];
		root[i]=root[i-1];
		while(now!=1) {
			int f=ne[now];
			int inv=qmi(ne[now],mod-2);
			vector<PII>v;
			LL fun=1;
			while(now%f==0) {
				fun*=f; now/=f;
				if(pre[fun]) v.pb({pre[fun],inv});
				pre[fun]=i;
			}
			insert(root[i],root[i],1,n,i,fun);
			LL all=1;
			for(int j=0;j<v.size();j++) {
				all*=v[j].Y,all%=mod;
				if(j==v.size()-1||v[j].X!=v[j+1].X) insert(root[i],root[i],1,n,v[j].X,all),all=1;
			}
		}
	}
	LL last=0;
	scanf("%d",&m);
	while(m--) {
		int l,r; scanf("%d%d",&l,&r);
		l=(1ll*l+last)%n+1; r=(1ll*r+last)%n+1;
		if(l>r) swap(l,r);
		printf("%lld\n",last=query(root[r],1,n,l,r));
	} 
 
 
 
	return 0;
}
/*
 
*/
 
 
 
 
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值