zstu月赛 1912: 我好像不会做 (欧拉函数+线段树)

题目描述

众所周知,duxing201606得到了某知名数学家的真传,现在她有了无穷的力量.现在她面前有一个数组a,duxing201606打算施法,有两种魔法,
   第一种:把[l,r]区间的每个数变成它的f函数,即a[i]=f(a[i]).
   第二种:查询区间[l,r]中a[i]的和.
   f(x)是1到x中与x互质的数的个数。f(1)=1.

输入

多组数据,请读到文件末尾.一个数n表示a的长度,(1<=n<=5e5).下一行n个数表示a[i],(1<=a[i]<=1e7).再下一行,一个q,表示执行的魔法个数.接下来q(1<=q<=5e5)行,先是操作种类,1表示魔法1,2表示魔法2,然后是区间l,r(1<=l<=r<=n)

输出

对于每个第二种操作,输出答案

样例输入

5
1 2 3 4 5
3
2 2 4
1 2 5
2 4 5

样例输出

9
6

这道题明显要使用欧拉函数,但我比赛时不会所以跳过了…
关于欧拉函数,这篇文章讲得比较好:
https://blog.csdn.net/liuzibujian/article/details/81086324

主要运用欧拉筛法,欧拉函数是积性函数等性质 o(n)求得欧拉函数

思路:线筛求phi,线段树维护a的区间和,由于phi最多执行log次就会变成1,直接暴力维护到叶子节点。判断一下是不是整个区间都是1,是的话就不用继续走了。

判断整个区间都是1的话就不用了更新了,然后直接普通的线段树就ok了
坑点:查询和树节点用 long long!!

#include<bits/stdc++.h>
#define fi first
#define se second
#define FOR(a) for(int i=0;i<a;i++)
#define show(a) cout<<a<<endl;
#define show2(a,b) cout<<a<<" "<<b<<endl;
#define show3(a,b,c) cout<<a<<" "<<b<<" "<<c<<endl;
using namespace std;

typedef long long ll;
typedef pair<int,int> P;
typedef pair<P,int> LP;
const ll inf=0x3f3f3f3f;
const int N=5e5+10;
const ll mod=998244353;

map<string,ll>  mp;
map<string ,int>ml;
const int mx=1e7+10;

int prime[mx];
ll tree[N<<2];
int phi[mx];
bool did[mx];
int n,m,x,t,tot=0;

void push_up(int rt)
{
	tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		scanf("%lld",&x);
		tree[rt]=x;
		return ;
	}
	int m=l+r >> 1;
	build(rt<<1,l,m);
	build(rt<<1|1,m+1,r);
	push_up(rt);
}

void update(int ql, int qr,int rt,int l,int r)
{
	if(tree[rt]==r-l+1) return;

	if(l==r&&l>=ql&&r<=qr)
	{
		tree[rt]=phi[tree[rt]];
		return ;
	}

	int m=l+r >> 1;
	if(ql<=m) update(ql,qr,rt<<1,l,m);
	if(qr>m) update(ql,qr,rt<<1|1,m+1,r);
	push_up(rt);
}


ll query(int ql,int qr,int rt,int l,int r)
{
	if(ql<=l&&qr>=r) return tree[rt];
	int m= l+r >>1;
	ll ans=0;
	if(ql<=m) ans+=query(ql,qr,rt<<1,l,m);
	if(qr>m) ans+=query(ql,qr,rt<<1|1,m+1,r);
	return ans;
}
int main()
{
	phi[1]=1;
	int tot=0;

	for(int i=2;i<=1e7;i++)
	{

		if(!did[i]) prime[++tot]=i,phi[i]=i-1;
		for(int j=1;j<=tot&&i*prime[j]<=1e7;j++)
		{
			did[i*prime[j]]=1;

			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
	//cout<<phi[1000000]<<endl;
	while(scanf("%d",&n)!=EOF)
	{
		memset(tree,0,sizeof tree);
		build(1,1,n);
		int q;
		scanf("%d",&q);
		while(q--)
		{
			int t,l,r;
			scanf("%d%d%d",&t,&l,&r);
			if(t==1)
			{
				update(l,r,1,1,n);
			}
			else
			{
				printf("%lld\n",query(l,r,1,1,n));
			}
		}
	}





}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值