BZOJ4869 [Shoi2017]相逢是问候

传送门

题解:
看过去一眼:线段树?暴力?然后就不知道该干什么了。
看题解:woc?这公式?(好吧不会这公式就做不成题了…)

欧拉定理EXT: a b ≡ a b m o d    ϕ ( p ) + ϕ ( p ) ( m o d   p ) , b ≥ ϕ ( p ) a^b\equiv a^{b\mod \phi(p)+\phi(p)} (mod\ p),b\ge \phi(p) ababmodϕ(p)+ϕ(p)(mod p),bϕ(p)

在这里就别想每次对p取模之后求幂了,样例就是个不错的反例。
那么我们可以证明最多暴力修改 O ( log ⁡ p ) O(\log p) O(logp)次值就不会改变了。
还有最后求结果要求到 ϕ ( 1 ) = 1 \phi(1)=1 ϕ(1)=1,不能求到 ϕ ( 2 ) = 1 \phi(2)=1 ϕ(2)=1就算完了。
我觉得这篇讲得挺详细的qwq。
然后我们就可以用线段树存一个操作次数,操作到了 ϕ ( 1 ) = 1 \phi(1)=1 ϕ(1)=1的次数就不用维护了。前一份代码的calc确实很醚…但能过。后面代码是修了一下锅的。
由于做的时候并不敢保证卡常,所以优化了一下。
每次快速幂都要花点时间,干脆预处理好了。
参考了这位dalao的思路,预处理一下 [ 0 , 10000 ] [0,10000] [0,10000]中每一个数的每一个 ϕ \phi ϕ的次方,然后再处理这些数的10000次方,砍掉一个log。

代码:

#include<cstdio>
#include<cmath>
#define maxn 50005
#define maxm 105
int n,m,p,c,pcnt,phi[maxm],p1[maxm][maxn],p2[maxm][maxn],a[maxn];
struct node { int val,sum,lazy; } tree[maxn*4];
int powmod(int a,int n,int mod)
{
    int res=1;
    while(n)
    {
        if(n&1) res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        n>>=1;
    }
    return res;
}
int Get(int x)
{
    int res=x,tmp=x;
    for(int i=2;i*i<=x;i++)
        if(tmp%i==0)
        {
            res=1ll*res*(i-1)/i;
            while(tmp%i==0) tmp/=i;
        }
    if(tmp>1) res=1ll*res*(tmp-1)/tmp;
    return res;
}
void init()
{
    phi[0]=p;
    while(phi[pcnt]>1) pcnt++,phi[pcnt]=Get(phi[pcnt-1]);
    phi[++pcnt]=1;
    for(int i=0;i<=pcnt;i++)
    {
        for(int j=0;j<=10000;j++) p1[i][j]=powmod(c,j,phi[i]);
        for(int j=0;j<=10000;j++) p2[i][j]=powmod(p1[i][10000],j,phi[i]);
    }
}
void Build(int i,int l,int r)
{
    if(l==r) { tree[i].val=0,tree[i].sum=a[l]; return; }
    int mid=(l+r)>>1;
    Build(i<<1,l,mid); Build(i<<1|1,mid+1,r);
    tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%p;
}
int Pow(int px,int n) { return 1ll*p2[px][n/10000]*p1[px][n%10000]%phi[px]; }
int calc(int x,int cnt)
{
    int res=x;
    for(int i=cnt;i>=1;i--)
    {
        if(res>=phi[i]) res=res%phi[i]+phi[i];
        res=Pow(i-1,res);
        if(!res) res=phi[i-1];
    }
    return res;
}
void Modify(int i,int l,int r,int ql,int qr)
{
    if(tree[i].lazy) return;
    if(l==r)
    {
        tree[i].sum=calc(a[l],++tree[i].val);
        if(tree[i].val==pcnt) tree[i].lazy=true;
        return;
    }
    int mid=(l+r)>>1;
    if(ql<=mid) Modify(i<<1,l,mid,ql,qr);
    if(qr>mid) Modify(i<<1|1,mid+1,r,ql,qr);
    tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%p,tree[i].lazy=tree[i<<1].lazy&tree[i<<1|1].lazy;
}
int Query(int i,int l,int r,int ql,int qr)
{
    if(ql<=l&&r<=qr) return tree[i].sum;
    int mid=(l+r)>>1;
    int res=0;
    if(ql<=mid) res=(res+Query(i<<1,l,mid,ql,qr))%p;
    if(qr>mid) res=(res+Query(i<<1|1,mid+1,r,ql,qr))%p;
    return res;
}
void Debug(int i,int l,int r)
{
    if(l==r) { printf("%d ",tree[i].sum); return; }
    int mid=(l+r)>>1;
    Debug(i<<1,l,mid); Debug(i<<1|1,mid+1,r);
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&p,&c);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    init();
    Build(1,1,n);
    while(m--)
    {
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);
        if(!op) Modify(1,1,n,l,r);
        else printf("%d\n",Query(1,1,n,l,r));
    }
}

Upd 2019 03 01: 修了点锅…总觉得上面代码不太严谨所以趁着老师把这道题拿出来讲的机会又搞了一遍…

#include<cstdio>
#include<cmath>
#define maxn 50005
#define maxm 105
#define N 16384
int n,m,p,c,pcnt,phi[maxm],p1[maxm][maxn],p2[maxm][maxn],a[maxn];
bool ok1[maxm][maxn],ok2[maxm][maxn];
struct node { int val,sum,lazy; } tree[maxn*4];
int powmod(int a,int n,int mod,bool &ok)
{
    int res=1,ok1=0; ok=0;
    while(n)
    {
        if(n&1) ok|=(1ll*res*a>=mod)|ok1,res=1ll*res*a%mod;
        ok1|=(1ll*a*a>=mod),a=1ll*a*a%mod;
        n>>=1;
    }
    return res;
}
int Get(int x)
{
    int res=x,tmp=x;
    for(int i=2;i*i<=x;i++)
        if(tmp%i==0)
        {
            res=1ll*res*(i-1)/i;
            while(tmp%i==0) tmp/=i;
        }
    if(tmp>1) res=1ll*res*(tmp-1)/tmp;
    return res;
}
void init()
{
    phi[0]=p;
    while(phi[pcnt]>1) pcnt++,phi[pcnt]=Get(phi[pcnt-1]);
    phi[++pcnt]=1;
    for(int i=0;i<=pcnt;i++)
    {
        for(int j=0;j<=N;j++) p1[i][j]=powmod(c,j,phi[i],ok1[i][j]);
        for(int j=0;j<=N;j++) p2[i][j]=powmod(p1[i][N],j,phi[i],ok2[i][j]);
    }
}
void Build(int i,int l,int r)
{
    if(l==r) { tree[i].val=0,tree[i].sum=a[l]; return; }
    int mid=(l+r)>>1;
    Build(i<<1,l,mid); Build(i<<1|1,mid+1,r);
    tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%p;
}
int Pow(int px,int n,bool &ok)
{ 
	ok=ok2[px][n/N]|ok1[px][n%N]|(1ll*p2[px][n/N]*p1[px][n%N]>=phi[px]);
	return 1ll*p2[px][n/N]*p1[px][n%N]%phi[px];
}
int calc(int x,int cnt)
{
	int res=x; bool ok;
	if(res>=phi[cnt]) res=res%phi[cnt]+phi[cnt];
	for(int i=cnt;i>=1;i--)
	{
		res=Pow(i-1,res,ok);
		if(ok) res+=phi[i-1];
	}
	return res%phi[0];
}
void Modify(int i,int l,int r,int ql,int qr)
{
    if(tree[i].lazy) return;
    if(l==r)
    {
        tree[i].sum=calc(a[l],++tree[i].val);
        if(tree[i].val==pcnt) tree[i].lazy=true;
        return;
    }
    int mid=(l+r)>>1;
    if(ql<=mid) Modify(i<<1,l,mid,ql,qr);
    if(qr>mid) Modify(i<<1|1,mid+1,r,ql,qr);
    tree[i].sum=(tree[i<<1].sum+tree[i<<1|1].sum)%p,tree[i].lazy=tree[i<<1].lazy&tree[i<<1|1].lazy;
}
int Query(int i,int l,int r,int ql,int qr)
{
    if(ql<=l&&r<=qr) return tree[i].sum;
    int mid=(l+r)>>1;
    int res=0;
    if(ql<=mid) res=(res+Query(i<<1,l,mid,ql,qr))%p;
    if(qr>mid) res=(res+Query(i<<1|1,mid+1,r,ql,qr))%p;
    return res;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&p,&c);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    init();
    Build(1,1,n);
    while(m--)
    {
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);
        if(!op) Modify(1,1,n,l,r);
        else printf("%d\n",Query(1,1,n,l,r));
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值