P3747 [六省联考 2017] 相逢是问候(欧拉定理、线段树、光速幂)

21 篇文章 0 订阅
19 篇文章 0 订阅

解析

洛谷你恶事做尽!
第三个tag在LOJ、bzoj等都是不需要的…
但在洛谷三只log根本过不去…
我谔谔。

如果做过 上帝与集合的正确用法 ,那么本题就并不难了。
打个表就可以发现,不断取欧拉函数的上限只有log级别,这使得我们暴力修改线段树复杂度就是正确的。

然后就做完了?
不!
这题也暴露了我数论知识及其不扎实的事实,拓展欧拉定理的完整内容为:
x b ≡ x b ( m o d p ) ( b < φ ( p ) ) x^b\equiv x^b\pmod p(b<\varphi(p)) xbxb(modp)(b<φ(p))
x b ≡ x b % φ ( p ) + φ ( p ) ( m o d p ) ( b ≥ φ ( p ) ) x^b\equiv x^{b\%\varphi(p)+\varphi(p)}\pmod p(b\ge\varphi(p)) xbxb%φ(p)+φ(p)(modp)(bφ(p))
上帝那个题之所以可以直接当第二条来做,是因为我们认为要求的那个东西绝对大于 φ ( p ) \varphi(p) φ(p)的。
本题则不然。
所以还需要在上一层快速幂的时候判断以下是否取过模,即是否达到了 φ ( p ) \varphi(p) φ(p)
总复杂度 O ( n log ⁡ 3 n ) O(n\log^3n) O(nlog3n)

然后就做完了?
不!
这个代码已经可以在LOJ上AC,但在洛谷打死也T#11。
我们发现,我们每次快速幂的时候的底数都是固定的,模数也就只是 p p p 不断取欧拉函数得到的 O ( log ⁡ ) O(\log) O(log) 个数而已。
所以我们可以对每个模数预处理出光速幂,这样就可以把快速幂的log拿掉了。
别忘了快速幂里还要判断是否取模。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=4e5+100;
const int M=2e5+100;
const int inf=1e9;

inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
bool flag;
const int B=4e4+100;
struct KSM{
	int c,mod,w;
	ll mi[B],Mi[B];
	bool jd[B],Jd[B];
	void init(int C,int Mod){
		c=C;mod=Mod;
		w=sqrt(2*mod);
		mi[0]=1;
		for(int i=1;i<=w;i++){
			mi[i]=mi[i-1]*c;
			jd[i]=jd[i-1];
			if(mi[i]>=mod){
				mi[i]%=mod;
				jd[i]=1;
			}
		}
		Mi[0]=1;
		Mi[1]=mi[w];Jd[1]=jd[w];
		for(int i=2;i<=2*mod/w;i++){
			Mi[i]=Mi[i-1]*Mi[1];
			Jd[i]=Jd[i-1];
			if(Mi[i]>=mod){
				Mi[i]%=mod;
				Jd[i]=1;
			}
		}
	}
	inline ll calc(ll k){
		if(k>2*mod){
			printf("k=%lld mod=%d\n",k,mod);
		}
		ll res=Mi[k/w]*mi[k%w];
		flag=Jd[k/w]|jd[k%w];
		if(res>=mod){
			res%=mod;
			flag=1;
		}		
		//printf("    k=%lld mod=%d %d %d jd=%d %d res=%lld\n",k,mod,k/w,k%w,Jd[k/w],jd[k%w],res);
		return res;
	}
}ksm[40];

inline int getphi(int x){
	int ans=x,top=sqrt(x);
	for(int i=2;i<=top;i++){
		if(x%i) continue;
		ans=1ll*ans*(i-1)/i;
		while(x%i==0) x/=i;
	}
	if(x>1){
		ans=1ll*ans*(x-1)/x;
	}
	return ans;
}

int n,m,mod,c;

int w[105];
inline int Solve(int k,int lim,int x,int mod){
	if(k>lim){	
		flag=x>=mod;	
		return x%mod;
	}
	if(mod<=1){
		flag=1;
		return 0;
	}
	int phi=w[k],pre=Solve(k+1,lim,x,phi);
	//printf("  k=%d pre=%d phi=%d mod=%d flag=%d mymod=%d\n",k,pre,phi,mod,flag,ksm[k].mod);
	//if(pre+flag*phi>ksm[k].mod*2){
	//	printf("  k=%d pre=%d phi=%d mod=%d flag=%d mi=%d mymod=%d\n",k,pre,phi,mod,flag,pre+flag*phi,ksm[k].mod);
//	}
	return ksm[k].calc(pre+flag*phi)%mod;
}
inline int solve(int lim,int x,int mod){
	return Solve(1,lim,x,mod);
}
int top;
void calc(int k,int x){
	int phi=getphi(x);
	w[k]=phi;
	if(x==1){
		top=k;
		return;
	}
	ksm[k].init(c,x);
	calc(k+1,phi);
}

int a[N];
#define mid ((l+r)>>1)
#define ls (k<<1)
#define rs (k<<1|1)
int mn[N<<2],sum[N<<2];
inline void pushup(int k){
	mn[k]=min(mn[ls],mn[rs]);
	sum[k]=(sum[ls]+sum[rs])%mod;
	return;
}
void build(int k,int l,int r){
	if(l==r){
		sum[k]=a[l];
		return;
	}
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(k);
}
int ask(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y) return sum[k];
	int res(0);
	if(x<=mid) res+=ask(ls,l,mid,x,y);
	if(y>mid) res+=ask(rs,mid+1,r,x,y);
	res%=mod;
	return res;
}
void change(int k,int l,int r,int x,int y){
	if(mn[k]>=top) return;
	if(l==r){
		mn[k]++;
		sum[k]=solve(mn[k],a[l],mod);
		//printf("k=%d pos=%d mn=%d sum=%d\n",k,l,mn[k],sum[k]);
		return;
	}
	if(x<=mid) change(ls,l,mid,x,y);
	if(y>mid) change(rs,mid+1,r,x,y);
	pushup(k);
}
void write(int x){
	if(x>9) write(x/10);
	putchar('0'+x%10);
}

signed main(){
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
#endif
	n=read();m=read();mod=read();c=read();
	w[0]=mod;
	calc(1,mod);
	//printf("top=%d\n",top);
	//for(int i=0;i<mod;i++){
	//	for(int j=0;j<=top;j++) printf("x=%d tim=%d %lld\n",i,j,solve(j,i,mod));
	//}
	//return 0;
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int op=read(),l=read(),r=read();//ok;
		if(op==0) change(1,1,n,l,r);
		else{
			write(ask(1,1,n,l,r));
			putchar('\n');
		}
	}
	return 0;
}
/*
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值