Euler Function--网络赛第二场(势能线段树)

这篇博客介绍了一种利用线段树和欧拉函数预处理来解决区间操作的问题。具体是给定一个数组和一系列操作,操作包括在区间内加数和求区间内欧拉函数值的和。通过预处理1到100的素数及其欧拉函数值,使用bitset维护质因数信息,并借助线段树进行区间修改和查询。在处理区间乘操作时,将数分解为质因子并逐个修改。博客提供了详细思路、样例输入输出及完整C++代码实现。
摘要由CSDN通过智能技术生成

题目链接:

网络赛Ⅱ L题

题意:

给定一个长度为n的数组x,有m次操作,操作有两种,操作0是让区间[l,r]内的数加上x,操作1是输出[l,r]区间内的\phi (x)(x的欧拉函数值)的和。

输入第一行给n,m,第二行给出x_1...x_n,接下来m行是m次操作。

思路:

首先预处理出1~100的所有素数和对应的欧拉函数值,用bitset维护100内的所有质数(tb[i][j]表示数字i的因子包不包含prime[j]),用cnt[i][j]数组维护数字i含有prime[j]的个数;

线段树维护的值有两个,一个是区间欧拉函数值的和,另一个是区间包含的公共素因子(代码中用bitset类型的tag,维护也很简单,共同素因子就直接左右子区间&就可以了)。

在进行区间乘的修改时,我们把数w分解成若干个质因子去进行修改,之前cnt数组中已经预处理出了1~100内所有数对应prime[j]的个数,将{j,cnt[w][j]}作为参数传给modify函数。

线段树在进行修改时,如果区间包含公共素因子prime[j],即tr[u].tag[j]==1,相当于区间内所有数的欧拉函数值都乘prime[j]^{cnt[i][j]},也就是整个区间欧拉和也乘prime[j]^{cnt[i][j]}

如果没有被包含,那么我们就交给子树来处理;

如果处理到叶子了还没被包含,那么直接暴力单点修改,叶子节点的欧拉函数值变成乘sum*p^{cnt[i][j]}*(1-\frac{1}{p}),sum是原欧拉函数值,p是prime[j],然后将tr[u].tag[j]置1。

样例:

输入

5 5
1 2 3 4 5
1 1 5
0 1 3 2
1 1 5
0 2 5 6
1 1 5

输出 

10
11
37

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int MOD=998244353;
const int N=1e5+10;
const int M=101;
int euler[M],prime[N];
bool isprime[M];
int n,m; 
int cnt[M][30];
bitset<30> tb[M];
int x[N];
typedef struct Node{
	int l,r;
	ll sum,lazy;
	bitset<30> tag;
}Node;
Node tr[N*4];
void init(){
	//求素数和欧拉函数 
	isprime[1]=1;
    for(int i=2;i<M;i++){
        if(!isprime[i]){
            prime[++prime[0]] = i;
            euler[i]=i-1;
        }
        for(int j=1;j<=prime[0]&&(ll)prime[j]*i<M;j++){
            int tp=prime[j]*i;
            isprime[tp]=1;
            if(i%prime[j]==0){
                euler[tp]=euler[i]*prime[j];
                break;
            }
            euler[tp]=euler[i]*(prime[j]-1);
        }
    }
    euler[1] = 1;
    //预处理出1~100含每个素数的个数
    int x;
	for(int i=1;i<M;i++){
		x=i;
		for(int j=1;j<=prime[0];j++){
			if(x<prime[j])
				break;
			while(x%prime[j]==0){
				tb[i][j]=1;
				cnt[i][j]++;
				x/=prime[j];
			}
		}
	} 
}

ll ksm(ll a,ll b){
	ll res=1;
	while(b){
		if(b%2)
			res=(res*a)%MOD;
		a=a*a%MOD;
		b=b/2;
	}
	return res;
}
void pushup(int u){
	tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%MOD;
	tr[u].tag=tr[u<<1].tag&tr[u<<1|1].tag;
}
void build(int u,int l,int r){
	tr[u].l=l,tr[u].r=r,tr[u].lazy=1;
	if(l==r){
		tr[u].sum=euler[x[l]];
		tr[u].tag=tb[x[l]];
		return ;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
//p.first是质数标号,p.second是质数个数 
void tag_union(int fa,int u){
	ll k=tr[fa].lazy;
	tr[u].lazy=(tr[u].lazy*k)%MOD;
	tr[u].sum=(tr[u].sum*k)%MOD;
}
void pushdown(int u){
	if(tr[u].lazy!=1){
		tag_union(u,u<<1);
		tag_union(u,u<<1|1);
		tr[u].lazy=1;
	}
}
void modify(int u,int l,int r,PII p){
	if(tr[u].l>=l&&tr[u].r<=r&&tr[u].tag[p.first]){
		ll k=ksm(prime[p.first],p.second);
		tr[u].lazy=(tr[u].lazy*k)%MOD;
		tr[u].sum=(tr[u].sum*k)%MOD;
		return ;
	}
	if(tr[u].l==tr[u].r){//已经到叶子节点了 
		ll k=ksm(prime[p.first],p.second-1);
		tr[u].sum=(tr[u].sum*k*(prime[p.first]-1))%MOD;
		tr[u].tag[p.first]=1;
		return ;
	}
	pushdown(u);
	int mid=(tr[u].l+tr[u].r)>>1;
	if(l<=mid)
		modify(u<<1,l,r,p);
	if(r>mid)
		modify(u<<1|1,l,r,p);
	pushup(u);
}
ll query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){
		return tr[u].sum;
	}
	pushdown(u);
	ll res=0;
	int mid=(tr[u].l+tr[u].r)>>1;
	if(l<=mid)
		res=query(u<<1,l,r);
	if(r>mid)
		res=(res+query(u<<1|1,l,r))%MOD;
	return res;
}
int main(){
	init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)	
		scanf("%d",&x[i]);
	build(1,1,n);
	int op,l,r,w;
	while(m--){
		scanf("%d",&op);
		if(op==0){
			scanf("%d%d%d",&l,&r,&w);
			for(int j=1;j<=prime[0];j++){
				if(prime[j]>w)
					break;
				if(cnt[w][j]){
					modify(1,l,r,{j,cnt[w][j]});
				}	
			}
		}
		else{
			scanf("%d%d",&l,&r);
			printf("%lld\n",query(1,l,r));
		}	
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值