[LNOI] 相逢是问候 || 扩展欧拉函数+线段树

原题为2017六省联考的D1T3

给出一个序列,m次操作,模数p和参数c
操作分为两种:
1、将[l,r]区间内的每个数x变为\(c^x\)
2、求[l,r]区间内数的和%p


首先,我们要了解一些数论姿势:
1、扩展欧拉定理
//我们熟知的费马小定理用于p是质数,欧拉定理用于a,p互质,而这道题都不满足这个限制
\((b>=\phi(p))\)时,\(a^b=a^{b\mod \phi(p) + \phi(p)}\)
2、(其实不算数论姿势)一个数最多经过log此\(\phi\)就会变成1

所以我们发现,一个数在经过几次变化后,指数永远是\(x\mod1+1\),也就是1,它就再也不变了!
//上面这里不理解的话,可以考虑这样一道题
//给定一个长度为n的序列a,每次询问区间[l,r] \(a_l^{a_{l+1}^{…}}\)的值。\(n<=10^5,m<=10^9\)
//原题应该是叫power tower

所以我们建一棵线段树,维护当前区间和和这个区间中被修改的最小次数。
预处理出对于mod p来讲,几次后一定会不变,记为k。每次修改将当前位置的修改次数++。如果这个区间的最小修改次数>=k,那么就不需要修改了,否则暴力修改。
因为每次的修改次数不一样,所以要用初值a[i]计算times后当前数会变为什么。

因为有n个数,每个数会被暴力修改\(\log(p)\)次,每次修改是\(\log(n)\)的,再乘上快速幂的复杂度\(\log\)就是\(O(nlog^3)\)
这样基本能过了,但是被卡常的话就不稳了。可以考虑预处理c的快速幂,反正c是固定的……
//bzoj不预处理就能过,洛谷过不了(也可能是博主写的比较菜)

贴一份没有预处理的代码

#include
#include
#define N 50010
#define M 10010
typedef long long ll;
using namespace std;
int a[N],n,m,prime[N],tot,p[N],P,c,op,l,r,k,MOD;
bool np[N];
struct hhh
{
    int l,r,sum,is;
}tre[4*N];
 
int read()
{
    int ans=0,fu=1;
    char j=getchar();
    for (;j<'0' || j>'9';j=getchar()) if (j=='-') fu=-1;
    for (;j>='0' && j<='9';j=getchar()) ans*=10,ans+=j-'0';
    return ans*fu;
}
 
int ksm(int x,int y,int mo,bool &flag)
{
    int ret=1;
    bool big=0;
    while (y)
    {
    if (y&1)
    {
        flag|=big|((ll)ret*x>=mo);
        ret=(ll)ret*x%mo;
    }
    if ((ll)x*x>=mo) big=1;
    x=(ll)x*x%mo;
    y>>=1;
    }
    return ret;
}
 
void build(int i,int l,int r)
{
    tre[i].l=l;tre[i].r=r;tre[i].is=0;
    if (l==r)
    {
    tre[i].sum=a[l]%P;
    return ;
    }
    int mid=(l+r)>>1;
    build(i*2,l,mid);
    build(i*2+1,mid+1,r);
    tre[i].sum=(tre[i*2].sum+tre[i*2+1].sum)%P;
}
 
int calc(int x,int dep)
{
    int ret=x;
    if (ret>=p[dep]) ret=ret%p[dep]+p[dep];
    while (dep)
    {
    dep--;
    bool flag=0;
    ret=ksm(c,ret,p[dep],flag);
    if (flag) ret+=p[dep];
    }
    return ret%P;
}
 
void modify(int i,int l,int r)
{
    if (l>tre[i].r || r=k) return ;
    if (tre[i].l==tre[i].r)
    {
    tre[i].is++;
    tre[i].sum=calc(a[tre[i].l],tre[i].is);
    return ;
    }
    modify(i*2,l,r);
    modify(i*2+1,l,r);
    tre[i].sum=(tre[i*2].sum+tre[i*2+1].sum)%P;
    tre[i].is=min(tre[i*2].is,tre[i*2+1].is);
}
 
int query(int i,int l,int r)
{
    if (l>tre[i].r || r=l && tre[i].r<=r) return tre[i].sum;
    return (query(i*2,l,r)+query(i*2+1,l,r))%P;
}
 
int phi(int n)
{
    int res=n,a=n;
    for(int i=2; i*i<=a; i++)
    if(a%i==0)
    {
        res=res/i*(i-1);
        while (a%i==0) a/=i;
    }
    if (a>1) res=res/a*(a-1);
    return res;
}
 
int main()
{
    n=read();m=read();P=read();c=read();
    for (int i=1;i<=n;i++) a[i]=read();
    p[0]=P;
    while (p[k]!=1) { ++k; p[k]=phi(p[k-1]); }
    p[++k]=1;
    build(1,1,n);
    while (m--)
    {
    op=read();l=read();r=read();
    if (op) printf("%d\n",query(1,l,r));
    else modify(1,l,r);
    }
    return 0;
}

你可能感兴趣的:([LNOI] 相逢是问候 || 扩展欧拉函数+线段树)