BZOJ4869 [Shoi2017]相逢是问候

题意:链接

维护一个长度为n的数组,m个操作,支持两种操作:
0 l r表示将第l个到第r个数中的每一个数ai替换为c^ai;
1 l r求第l个到第r个数的和。

思路:

首先要知道扩展欧拉定理:如果 a > p h i ( p ) , c a ≡ c ( a m o d    p h i ( p ) ) + p h i ( p ) m o d    p a>phi(p) ,c^{a}\equiv c^{(a\mod phi(p))+phi(p)} \mod p a>phi(p),cac(amodphi(p))+phi(p)modp
在多次执行替换操作后, a [ i ] a[i] a[i]将会变为 c c c . . . m o d    p h i ( 1 ) + p h i ( 1 ) c^{c^{c^{...}\mod phi(1) + phi(1) }} ccc...modphi(1)+phi(1),然后它的值就不会在改变了。
假设在不断取 p h i phi phi的情况下, p p p经过 k k k次变为1,易知 k &lt; = l o g ( p ) k&lt;=log(p) k<=log(p)
所以,我们可以用线段树维护区间和,以及区间最少的替换操作次数。
当它的替换操作次数小于 k k k时,直接暴力修改它的值。
当区间最少的替换操作次数都大于等于 k k k时,这段区间和就不会改变了。
时间复杂度 O ( m l o g ( n ) l o g 2 ( p ) ) O(mlog(n)log^2(p)) O(mlog(n)log2(p))
注意 p h i phi phi必须取到1,不能再 p h i ( 2 ) = 1 phi(2)=1 phi(2)=1时结束。
详见代码:

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 50010
int PowMod(int a,int b,int MO,bool &f)
{
	int ret=1,t=0;
	while(b)
	{
		if(b&1) f|=t|(1LL*ret*a>=MO),ret=1LL*ret*a%MO;
		t|=(1LL*a*a>=MO);
		a=1LL*a*a%MO;
		b>>=1;
	}
	return ret;
}
int a[MAXN],p[MAXN],c,n,m,K;
int Work(int x,int dep)
{
	int ret=x;
	if(ret>=p[dep]) ret=ret%p[dep]+p[dep];
	while(dep)
	{
		dep--;
		bool flag=0;
		ret=PowMod(c,ret,p[dep],flag);
		if(flag) ret+=p[dep];
	}
    return ret%p[dep];
}
int Phi(int x)
{
	int ret=x;
	for(int i=2;1LL*i*i<=x;i++)
		if(x%i==0)
		{
			while(x%i==0) x/=i;
			ret=ret/i*(i-1);
		}
	if(x!=1) ret=ret/x*(x-1);
	return ret;
}
int sum[MAXN*4],tag[MAXN*4];
void PushUp(int i)
{
	sum[i]=(sum[i<<1]+sum[i<<1|1])%p[0];
	tag[i]=min(tag[i<<1],tag[i<<1|1]);
}
void Build(int i,int l,int r)
{
	if(l==r)
	{
		sum[i]=a[l];tag[i]=0;
		return;
	}
	int mid=(l+r)>>1;
	Build(i<<1,l,mid);
	Build(i<<1|1,mid+1,r);
	PushUp(i);
}
void Modify(int i,int l,int r,int L,int R)
{
	if(tag[i]==K) return;
	if(l==r)
	{
		tag[i]++;
		sum[i]=Work(a[l],tag[i]);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid) Modify(i<<1,l,mid,L,R);
	if(R>mid) Modify(i<<1|1,mid+1,r,L,R);
	PushUp(i);
}
int Ask(int i,int l,int r,int L,int R)
{
	if(L<=l&&r<=R) return sum[i];
	int ret=0,mid=(l+r)>>1;
	if(L<=mid) ret=Ask(i<<1,l,mid,L,R);
	if(R>mid) ret=(ret+Ask(i<<1|1,mid+1,r,L,R))%p[0];
	return ret;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&p[0],&c);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	while(p[K]!=1) p[++K]=Phi(p[K-1]);	p[++K]=1;
	Build(1,1,n);
	int f,l,r;
	while(m--)
	{
		scanf("%d%d%d",&f,&l,&r);
		if(f==0) Modify(1,1,n,l,r);
		else printf("%d\n",Ask(1,1,n,l,r));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值