洛谷4140 奇数国

题目大意

给定一个初值全为3、长度为100000的序列,并实时改变其中的值,求出给定区间内所有数的乘积product后,问有多少个小于等于product的正整数number满足这个式子:

n u m b e r ∗ x + p r o d u c t ∗ y = 1 number*x+product*y=1 numberx+producty=1
其中x,y为任意整数,答案对19961993取模。


知识储备

  1. 裴蜀定理

a ∗ x + b ∗ y = m a*x+b*y=m ax+by=m
有整数解当且仅当m为gcd(a,b)的倍数。

推论:
只有当a,b互质(即gcd(a,b)=1)时,等式 a ∗ x + b ∗ y = 1 a*x+b*y=1 ax+by=1有整数解

  1. 欧拉函数

φ ( n ) = n ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p i )    ( 其 中 p 为 n 的 质 因 子 ) φ(n)=n*(1-\frac {1} {p1})*(1-\frac {1} {p2})*...*(1-\frac {1} {pi})\ \ (其中p为n的质因子) φ(n)=n(1p11)(1p21)...(1pi1)  (pn)

表示1~n之间与n互质的数的个数。


思路

一如既往的一脸懵逼,直到听了大佬的讲解才豁然开朗。

首先,题目前三段那么多话,就是为了告诉你要求出给定区间内的所有数的乘积,并贴心的给出分解质因数的提示(财产只可能由前60个质数组成,不会出现大得吓人的质数)

于是大佬说:开60个树状数组,分别维护一段区间内的财产中不同的质因子共有多少个,(如树状数组1维护第一个质数2的个数,树状数组2维护第二个质数3的个数,以此类推…)对财产求和时再判断该区间内是否含有该质因子,如果有,就乘上相应的次数,这样就把乘积求出来了。

修改时,则要先对原先的钱分解质因数,并减去它的贡献,才能再加上修改后的值的贡献。

接下来考虑第二步, n u m b e r ∗ x + p r o d u c t ∗ y = 1 number*x+product*y=1 numberx+producty=1代表什么意思?(大佬说:“真笨,连裴蜀定理也不知道”)

咳咳咳…

有了上面的知识储备,我们知道,只有当number与product互质时,等式才能成立,而number取值范围为1~product,问题就转化为求1~product中与product互质的数的个数,即product的欧拉函数。

有了上面的公式,我们只需要在对财产求和时,判断是否含有该质因子,如果有,则乘上 p i − 1 pi-1 pi1再除以 p i pi pi,(合起来即 1 − 1 p i 1-\frac{1}{pi} 1pi1)最后乘上求出的product,即product的欧拉函数值。

(好吧,大佬又教了我一种更秀的方法,果然数学好就是厉害,差点就去整逆元了)


代码

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int N=100000,mod=19961993;
ll n,op,x,y,cnt,pro;
int money[N+1],tr[100][N+1],prime[100];
void update(int i,int x,int val)//修改树状数组里面某个银行里质因数的个数 
{
    
    
	for(;x<=N;x+=x&-x)
	tr[i][x]+=val;
}
int query(int i,int x)//查询树状数组里面一段区间内质因数的个数
{
    
    
	int sum=0;
	for(;x;x-=x&-x) sum+=tr[i][x];
	return sum;
}
void divide(int k,int x,int f)
{
    
    
	int sum;
	for(int i=1;i<=60;i++)
	{
    
    
		sum=0;
		while(k%prime[i]==0) k/=prime[i],sum++;
		if(sum) update(i,x,f*sum);//分解质因数后修改对应树状数组里面的值 
	}
}
bool judge(int x)
{
    
    
	for(int i=2;i*i<=x;i++)
	if(x%i==0) return false;
	return true;
}
ll ksm(ll k,ll p)
{
    
    
	ll pro=1;
	while(p)
	{
    
    
		if(p&1ll) pro=(pro*k)%mod;
		p>>=1;
		k=k*k%mod;
	}
	return pro%mod;
}
int main()
{
    
    
	for(int i=2;i<=281;i++)
	if(judge(i)) prime[++cnt]=i;
	scanf("%lld",&n);
	for(int i=1;i<=N;i++) 
	money[i]=3,update(2,i,1);//银行里的钱全初始化为3 
	for(int i=1;i<=n;i++)
	{
    
    
		scanf("%lld%lld%lld",&op,&x,&y);
		if(op)
		{
    
    
			divide(money[x],x,-1);//将原先money[x]的贡献减去 
			money[x]=y;
			divide(money[x],x,1);//加上现在money[x]的贡献 
		}
		else
		{
    
    
			pro=1;
			for(int i=1;i<=60;i++)
			{
    
    
				int k=query(i,y)-query(i,x-1);//k为区间x~y中质因子prime[i]的个数 
				if(k)
				{
    
    
					pro=pro*ksm(prime[i],k-1)%mod;//将k个质因数累乘起来 
					pro=pro*(prime[i]-1)%mod;//求phi ,因为要除以一个prime[i],所以上面少乘一个prime[i](否则要用逆元) 
				}//phi(x)=x*(1-1/p1)*(1-1/p2)*...*(1-1/pn)
			}
			printf("%lld\n",pro%mod);
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45383207/article/details/112984421