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

题目链接:

网络赛Ⅱ 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));
		}	
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值