【题解】CF1422F Boring Queries

0:暴力

我们只需要统计区间中,每个素数在一个数中分解出的数量最大值

然后累乘

如果对于每个素数都用ST表统计,几万个ST表肯定会时空双爆

1:根号分治

这么多的素数我们无法存下,我们能不能缩减一下范围呢?我们偶然发现 \(<=\sqrt{N}\) 的素数个数只有 \(87\)

考虑根号分治,每个数对于所有素数 \(x\) 满足 \(x<=\sqrt{N}\),进行素因数分解

对于这 \(87\) 个素数开char类型的ST表(防止爆空间)

其他数分解之后,肯定是 \(1\) 或者是 \(> \sqrt{N}\) 的素数,定义此数组为 \(b\)

我们接下来只需要统计区间中数的乘积(重复的数只统计一次)

2:主席树

别想了,肯定不可以算前缀积

我们继续分析

定义 \(pre_i: [1,i-1]\) 中和它数值相等的下标集合的最大值,如果没有则为 \(0\)

设询问区间为 \([L,R]\), 我们发现一个数会被统计,当且仅当其其下标 \(tp\) 满足 $ L<=tp<=R $ 且 $ pre<L $

我们可以对每一个下标 \(g\) 开一颗线段树,如果叶子节点所在下标 \(i\)\(pre_i<=g\) ,则其值为 \(b_i\),否则为 \(1\)

其他节点为其区间的乘积

3:处理

对于 \(<=\sqrt{N}\) 的每个素数,st表算其在 \([L,R]\) 中的每个数分解出的数量最大值,快速幂统计

然后乘上第 \(l-1\) 个线段树上 \([L,R]\) 的乘积

4:代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,sqrtN=sqrt(2*N)+3,SumP=89,logN=18,mod=1e9+7;
int a[N];
vector<int> P;
bool p[sqrtN];
char st[SumP][logN][N];

int n;
void stinit(int tp){
	for(int k=1;k<logN;k++){
		for(int i=1;i<=n;i++){
			st[tp][k][i]=max(st[tp][k-1][i],st[tp][k-1][i+(1<<k-1)]);
		}
	}
}
int stask(int tp,int l,int r){
	int k=log2(r-l+1);
	return max(st[tp][k][l],st[tp][k][r-(1<<k)+1]);
}
int now[N*2];
int nxt[N];
int pre[N];

struct node{
	int l,r;
	int mul;
	int son[2];
	#define l(q) tree[q].l
	#define r(q) tree[q].r
	#define mul(q) tree[q].mul
	#define ls(q) tree[q].son[0]
	#define rs(q) tree[q].son[1]
}tree[N*35];
int idx=0;
int root[N];

void push_up(int q){
	mul(q)=1LL*mul(ls(q))*mul(rs(q))%mod;
}
int build(int l,int r){
	int q=++idx;
	l(q)=l;r(q)=r;
	if(l==r){
		if(pre[l]==0) mul(q)=a[l];
		else mul(q)=1;
		return q;
	}
	int mid=l+r>>1;
	ls(q)=build(l,mid);
	rs(q)=build(mid+1,r);
	push_up(q);
	return q;
}
void update(int las,int &now,int tp){
	now=++idx;
	int l=l(las),r=r(las);
	tree[now]=tree[las];
	if(l==r){
		mul(now)=a[l];
		return ;
	}
	int mid=l+r>>1;
	if(tp<=mid) update(ls(las),ls(now),tp);
	else update(rs(las),rs(now),tp);
	push_up(now);
}
int query(int q,int L,int R){
	int l=l(q),r=r(q);
	//cout<<q<<" "<<l<<" "<<r<<"\n";
	if(L<=l&&r<=R){
		//cout<<l<<" "<<r<<" "<<mul(q)<<"\n";
		return mul(q);
	}
	
	int ans=1;
	int mid=l+r>>1;
	if(L<=mid) ans*=query(ls(q),L,R);ans%=mod;
	if(mid<R) ans*=query(rs(q),L,R);ans%=mod;
	//cout<<l<<" "<<r<<" "<<ans<<"\n";
	return ans;
}

int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1){
			res*=a;
			res%=mod;
		}
		a*=a;a%=mod;
		b>>=1;
	}
	return res;
}
void dfs(int q){
	int l=l(q),r=r(q);
	cout<<l<<" "<<r<<" "<<mul(q)<<"\n";
	if(l==r) return ;
	dfs(ls(q));dfs(rs(q));
}
signed main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	for(int i=2;i<sqrtN;i++){
		if(!p[i]) P.push_back(i);
		for(int j=0;j<P.size()&&P[j]*i<=sqrtN;j++){
			p[P[j]*i]=1;
			if(i%P[j]==0) break;
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=0;j<P.size();j++){
			if(a[i]%P[j]==0){
				int res=0;
				while(a[i]%P[j]==0){
					res++;
					a[i]/=P[j];
				}
				st[j][0][i]=res;
			}
		}
	}
	for(int i=0;i<SumP;i++) stinit(i);
	
	//for(int i=1;i<=n;i++) cout<<a[i]<<" ";cout<<"\n";
	for(int i=0;i<2*N;i++) now[i]=n+1;
	for(int i=n;i>=1;i--){
		nxt[i]=now[a[i]];
		now[a[i]]=i;
	}
	for(int i=0;i<2*N;i++) now[i]=0;
	for(int i=1;i<=n;i++){
		pre[i]=now[a[i]];
		now[a[i]]=i;
		//cout<<pre[i]<<" ";
	}
	
	root[1]=build(1,n);
	//dfs(root[1]);
	for(int i=2;i<=n;i++){
		if(nxt[i-1]==n+1) root[i]=root[i-1];
		else update(root[i-1],root[i],nxt[i-1]);
	}
	
	//dfs(root[1]);
	int m;cin>>m;
	int las=0;
	//cout<<2<<" "<<P[2]<<" "<<stask(2,1,3)<<"\n";
	while(m--){
		int l,r;cin>>l>>r;
		l=(l+las)%n+1;r=(r+las)%n+1;
		if(l>r) swap(l,r);
		//cout<<"-->"<<l<<" "<<r<<"\n";
		int ans=1;
		//cout<<"------>"<<2<<" "<<P[2]<<" "<<stask(2,1,3)<<"\n";
		for(int i=0;i<SumP;i++){
			//cout<<i<<" "<<P[i]<<" "<<stask(i,l,r)<<"\n";
			ans*=ksm(P[i],stask(i,l,r));
			ans%=mod;
		}
		//cout<<ans<<" ";
		ans*=query(root[l],l,r);ans%=mod;
		//cout<<"-->"<<ans<<"\n";
		cout<<ans<<"\n";
		las=ans;
	}
	return 0;
}

5:历程

5.1:山重水复

想用map搞线段树每个区间的素数以及其出现次数,最后8亿次运算被卡掉

其实可以发现可以用数组代替map维护线段树(像是倒退),然后发现时空双爆

5.2:柳暗花明

静态?我用啥线段树,直接转成 \(st\) 表,仍然时空双爆,不过好了很多

考虑缩小范围,发现根号分治

分解之后的数组全部是单个素数或 \(1\)

要统计其累乘,我们分析其性质,发现可以用主席树维护

AC

6:反思

6.1

可以记住其解法,因为这个问题优美简洁,可能作为其他问题的子问题

6.2

有时候的退步只是暂时的,它是迈向成功的必经之路

所以对于一些退步的做法,我们可以保留

6.3

一些复杂问题可能难点只在关键的突破口,如本题根号分治的发现

6.4

一些看起来简单的问题,可能不一定简单,如本题求lcm,和求区间不重乘积

他们看起来很简单,但是其求解是很复杂的

posted @ 2026-07-03 20:32  Aistyr  阅读(4)  评论(0)    收藏  举报