洛谷 B3645 数列前缀和 2 题解

前缀知识:枚举,费马小定理,逆元,线性乘法逆元,线段树(?)。

解法1:暴力

如题。暴力枚举即可,30分。由于太简单,不给代码。

解法2:前缀积+费马小定理+逆元

由于涉及静态区间,可以想到前缀积。前缀积公式为 \(q_r/q_{l-1}\),除法恰好可以用逆元来算。直接写即可。不会超时,因为时间为 \(O(n\log p)\),由于 \(p\) 很小,不会超时。但是仍然不够优秀。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,p=1145141;
int n,Q,a[N],q[N],ans;
int ksm(int a,int b){
	int ans=1;
	a%=p;
	while(b){
		if(b&1){
			ans=ans*a%p;
		}
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
signed main(){
	//freopen("xx.in","r",stdin);
	//freopen("xx.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>Q;
	q[0]=1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		q[i]=q[i-1]*a[i]%p;
	}
	while(Q--){
		int l,r;
		cin>>l>>r;
		ans^=(q[r]*ksm(q[l-1],p-2)%p);
	}
	cout<<ans;
	return 0;
}

解法3:线性乘法逆元

由于 \(p\) 很小,可以想到线性乘法逆元。可以预处理出所有情况,这样查询就是 \(O(1)\) 了。但是预处理非常差劲,所以实际时间上不如上一种。(luogu数据的问题?

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,p=1145141;
int n,Q,a[N],q[N],c[p],ans;
int ksm(int a,int b){
	int ans=1;
	a%=p;
	while(b){
		if(b&1){
			ans=ans*a%p;
		}
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
signed main(){
	//freopen("xx.in","r",stdin);
	//freopen("xx.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	c[1]=1;
	for(int i=2;i<p;i++){
		c[i]=c[p%i]*(p-p/i)%p;
	}
	cin>>n>>Q;
	q[0]=1;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		q[i]=q[i-1]*a[i]%p;
	}
	while(Q--){
		int l,r;
		cin>>l>>r;
		ans^=(q[r]*c[q[l-1]]%p);
	}
	cout<<ans;
	return 0;
}

解法4:线段树

没错,由于是区间,所以我们的线段树来了!用线段树不需要任何的数论知识,这意味着不会数学的小学生都能做!线段树是万能的!(线段树做法纯属瞎搞,请勿模仿)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,p=1145141;
int n,q,a[N],t[N*4],ans;
void build(int now,int tl,int tr){
	if(tl==tr){
		t[now]=a[tl];
		return ;
	}
	int mid=(tl+tr)/2;
	build(now*2,tl,mid);
	build(now*2+1,mid+1,tr);
	t[now]=t[now*2]*t[now*2+1]%p;
}
int query(int now,int tl,int tr,int l,int r){
	if(tl>=l&&tr<=r){
		return t[now];
	}
	if(tl>r||tr<l){
		return 1;
	}
	int mid=(tl+tr)/2;
	return query(now*2,tl,mid,l,r)*query(now*2+1,mid+1,tr,l,r)%p;
}
signed main(){
	//freopen("xx.in","r",stdin);
	//freopen("xx.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	while(q--){
		int l,r;
		cin>>l>>r;
		ans^=query(1,1,n,l,r);
	}
	cout<<ans;
	return 0;
}

总结

此题的做法很多,最好的一种是线段树,算是一个比较模板的题,所以没有太多讲解。

posted @ 2024-09-04 18:30  吴一鸣  阅读(62)  评论(0编辑  收藏  举报