zoukankan      html  css  js  c++  java
  • 题解 Luogu P3747 [六省联考 2017]相逢是问候

    题意

    给定长度为 (n) 的数列 (a),要求支持 (m) 次以下操作:

    • 将区间 ([l,r]) 的每一个 (a_i) 替换为 (c^{a_i}),其中 (c) 是给定的常数
    • 询问 ([l,r]) 的区间和,对 (p) 取模

    (1 leq n,m leq 5 imes 10^4,1 leq p leq 10^8,0 leq a_i < p,0 < c < p)

    题解

    观察到结果要对 (p) 取模。考虑 (a_i) 进行若干次操作后,变为了

    [c^{c^{{cdots^{a_i}}}} mod p ]

    根据扩展欧拉定理,在 (c^{cdots^{a_i}}) 大于 (varphi(p)) 的情况下,上式等于

    [c^{c^{{cdots^{a_i}}} mod varphi(p) + varphi(p)} mod p ]

    (c^{cdots^{a_i}} mod varphi(p)) 在指数大于 (varphi(varphi(p))) 的情况下又等于

    [c^{cdots^{a_i} mod varphi(varphi(p)) + varphi(varphi(p))} mod varphi(p) ]

    这样递归下去,必定会出现指数小于某个 (varphi) 函数的情况,或者那一层对应的 (varphi) 函数为 (1)。观察到,如果递归到某一层时,(varphi) 函数为 (1),那么接下来的所有操作都是不会影响答案 —— 那一层的指数必定是 (1)

    我们可以证明当某一层对应的 (varphi) 函数值为 (1) 时,递归层数不超过 (O(log p)) 层。当某一层的 (varphi) 函数值为 (x) 时,如果 (x) 是偶数,则 (varphi(x) leq dfrac{1}{2}x)。如果 (x) 是奇数,根据 (varphi) 的计算式,(varphi(x)) 是偶数。那么经过 (O(log p)) 层递归后,(varphi(x)) 一定为 (1)

    于是我们可以记录下递归层数 (c) 并暴力修改。如果某个区间修改次数的最小值已经大于 (c),那么我们就不需要修改了(类似于区间开根那道题的思路)。

    算上快速幂的复杂度,这样做是 (O(n log ^2n log p)) 的。我们需要预处理幂次(类似于 BSGS 算法,预处理 (c^{0...10^4})(c^{10^4},c^{2 imes 10^4},...,c^{10^4 imes 10^4}),每次询问按照把两边乘起来),这样单次修改就可以降到 (O(log ^2n)),总复杂度 (O(n log ^2 n))

    # include <bits/stdc++.h>
    # define int long long
    const int N=100010,SQRT=10005,MAXX=10000,INF=0x3f3f3f3f;
    
    struct Node{
    	int sum;
    	int minx;
    }tree[N<<2];
    
    int bpow[SQRT][40],gpow[SQRT][40];
    bool overb[SQRT][40],overg[SQRT][40];
    
    int n,m,MOD,c;
    int p[N],pcnt;
    int a[N];
    
    inline int read(void){
    	int res,f=1;
    	char c;
    	while((c=getchar())<'0'||c>'9')
    		if(c=='-')f=-1;
    	res=c-48;
    	while((c=getchar())>='0'&&c<='9')
    		res=res*10+c-48;
    	return res*f;
    }
    inline int getphi(int x){ // 计算 phi
    	int temp=x,phi=x;
    	for(int i=2;i*i<=temp;++i){
    		if(temp%i==0){
    			phi-=phi/i;
    			while(temp%i==0)
    				temp/=i;
    		}
    	}
    	if(temp>1)
    		phi-=phi/temp;
    	return phi;
    }
    inline void init_pow(void){ // 计算 c 的幂次 0..10^4
    	for(int i=0;i<=pcnt;++i){ // 表示是第 i 层递归的模数
    		bpow[0][i]=1;
    		for(int j=1;j<=MAXX;++j){
    			bpow[j][i]=bpow[j-1][i]*c;
    			if(bpow[j][i]>=p[i]){ // 记录下,已经大于模数,这样就可以使用扩展欧拉定理
    				overb[j][i]=true,bpow[j][i]%=p[i];
    			}
    			overb[j][i]|=overb[j-1][i]; // 如果前一个幂就大于了当然也行
    		}
    	}
    	for(int i=0;i<=pcnt;++i){ // 计算 c 的幂次 10^4...10^8
    		gpow[0][i]=1;
    		for(int j=1;j<=MAXX;++j){
    			gpow[j][i]=gpow[j-1][i]*bpow[MAXX][i];
    			if(gpow[j][i]>=p[i]){
    				overg[j][i]=true,gpow[j][i]%=p[i];
    			}
    			overg[j][i]|=overg[j-1][i];
    		}
    	}
    	return;
    }
    inline std::pair <int,bool> qpow(int val,int i){ // 计算 c^val mod p[i]
    	int bstep=val%MAXX,gstep=val/MAXX,res=bpow[bstep][i]*gpow[gstep][i];
    	bool f=overb[bstep][i]||overg[gstep][i]; 
    	if(res>=p[i]){ // 扩展欧拉定理
    		f=true,res%=p[i];
    	}
    	return std::make_pair(res,f);
    }
    inline int calc(int val,int i){
    	int now=val;
    	if(now>=p[i]) // 扩展欧拉定理
    		now=now%p[i]+p[i];
    	for(;i;--i){ // 递归计算
    		std::pair <int,bool> res=qpow(now,i-1); // 上一层的答案作为这一层的指数
    		now=res.first;
    		if(res.second)
    			now+=p[i-1];
    	}
    	return now;
    }
    inline int lc(int x){
    	return x<<1;
    }
    inline int rc(int x){
    	return x<<1|1;
    }
    inline void pushup(int k){
    	tree[k].minx=std::min(tree[lc(k)].minx,tree[rc(k)].minx);
    	tree[k].sum=(tree[lc(k)].sum+tree[rc(k)].sum)%MOD;
    }
    void build(int k,int l,int r){
    	if(l==r){
    		tree[k].sum=a[l];
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(lc(k),l,mid),build(rc(k),mid+1,r);
    	pushup(k);
    	return;
    }
    void change(int k,int l,int r,int L,int R){
    	if(tree[k].minx>=pcnt)
    		return;
    	if(l==r){
    		++tree[k].minx,tree[k].sum=calc(a[l],tree[k].minx);
    		return; 
    	}
    	int mid=(l+r)>>1;
    	if(L<=mid)
    		change(lc(k),l,mid,L,R);
    	if(mid<R)
    		change(rc(k),mid+1,r,L,R);
    	pushup(k);
    	return;
    }
    int query(int k,int l,int r,int L,int R){
    	if(L<=l&&r<=R){
    		return tree[k].sum;
    	}
    	int mid=(l+r)>>1,res=0;
    	if(L<=mid)
    		res+=query(lc(k),l,mid,L,R);
    	if(mid<R)
    		res=(res+query(rc(k),mid+1,r,L,R))%MOD;
    	return res;
    }
    
    # undef int
    int main(void){
    # define int long long
    	n=read(),m=read(),MOD=read(),c=read();
    	for(int i=1;i<=n;++i){
    		a[i]=read();
    	}
    	p[0]=MOD;
    	while(p[pcnt]>1)
    		++pcnt,p[pcnt]=getphi(p[pcnt-1]);
    	p[++pcnt]=1; // 因为线段树里用了 >= 于是要多一个
        // 当然也可以无视这一句,然后把线段树里 tree[k].minx>=pcnt 改为 >
    	init_pow();
    	build(1,1,n);
    	int opt,l,r;
    	while(m--){
    		opt=read(),l=read(),r=read();
    		if(!opt){
    			change(1,1,n,l,r);
    		}else{
    			printf("%lld
    ",query(1,1,n,l,r)%MOD);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    第一次冲刺结果演示 一组评审总结
    检查博客情况
    第十次站立会议
    第九次站立会议
    暑期实训day4
    暑期实训day3
    暑期实训day2.1——一发空格引发的血案
    暑期实训day2
    暑期实训day1
    黑板模式
  • 原文地址:https://www.cnblogs.com/liuzongxin/p/15256827.html
Copyright © 2011-2022 走看看