【BZOJ 4869】【2017六省联考】相逢是问候

考虑一个欧拉定理的扩展:
x>φ(p) 时有:
cxcx%φ(p)+φ(p)(modp)
也叫作欧拉定理EXT,证明看这里:https://zhuanlan.zhihu.com/p/24902174
这个公式的强大之处在于a和p可以不互质,也就是说p可以不为质数。
然后证明一个结论:这个操作在操作 logP 次之后一定是一个定值。
显然可以发现一个数 P 在最多取O(logP)次欧拉函数之后会变成1,(一个简单的证明:奇数变偶数)。
然后就可以发现在不断地套用欧拉定理的时候,指数模 φ(p) ,指数的指数模 φ(φ(p)) …………最多 O(logP) 次之后变成模1加1,也就是1,之后不管有多少指数都会变成1,所以一个数在操作 O(logP) 次之后再操作就没有意义了。(和http://uoj.ac/problem/228很像,都是操作一定次数之后不变)
考虑使用线段树来做,如果当前区间全部已经不再变化就退出,否则暴力修改下去。所以最坏情况下每个数会修改 O(logP) 次,每次修改会修改树上 O(logN) 个节点,所以时间复杂度是 O(NlogNlogP)
但是在这里我们默认了修改一次的时间是 O(1) ,然而事实并非如此。对于一个数的快速幂,需要递归 O(logP) 层(因为每层指数的模数都是不一样的,都要重新算),所以一个数快速幂就要两个log,一共有 nlogP 个数(对不是n个数, xcxccx 这些都要重新算的,我开始就是这里少算了一个log),所以快速幂的预处理就变成了三个log!虽然开了O2,但是如果常数爆炸的话很有可能被卡掉。
接着我们尝试优化掉一个log:计算快速幂的那个log。为什么可以优化掉呢?因为底数一定是c,而指数最大是1e8,那么我们可以预处理 cx tx ,其中 t=c10000x<=10000 ,对于任何一个幂,指数大于10000的部分从 tx 的表中找,小于10000的部分从 cx 的表中找,这样总的时间复杂度降到两个log。

#include<cmath>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define N 50005
#define ls x<<1
#define rs x<<1|1
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int sum[N*4],num[N*4],tag[N*4],a[N];
int f[N][100],cc[N][100],ex_c[N][100],pp[100];
int n,m,p,c,k,i,opt,l,r;
int quick_power(int x,int a,int mo)
{
    int res = 1;
    while (a) {if (a&1) res=(1ll*res*x)%mo; x=(1ll*x*x)%mo; a>>=1;}
    return res;
}
void pushup(int x) {sum[x] = (sum[ls] + sum[rs]) % p; tag[x] = tag[ls] + tag[rs];}
void build(int x,int l,int r)
{
    if (l == r) {sum[x] = a[l]; num[x] = 0; tag[x] = r - l + 1; return;}
    int mid = (l + r) >> 1; build(ls,l,mid); build(rs,mid+1,r);
    pushup(x);
}
void change(int x,int l,int r,int L,int R)
{
    if (L <= l && r <= R && !tag[x]) return;
    if (l == r) 
        {
            num[x]++; sum[x] = f[l][num[x]];
            if (num[x] == k) tag[x]--;
            return;
        }
    int mid = (l + r) >> 1;
    if (L <= mid) change(ls,l,mid,L,R); if (mid < R) change(rs,mid+1,r,L,R);
    pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
    if (L <= l && r <= R) return sum[x];
    int mid = (l + r) >> 1; ll res = 0;
    if (L <= mid) res += query(ls,l,mid,L,R); 
    if (mid < R) res += query(rs,mid+1,r,L,R);
    return res % p;
}
int phi(int x)
{
    int i,res = x;
    fo(i,2,sqrt(x)) if (!(x%i)) {while (!(x%i)) x/=i;res = res/i*(i-1);}
    if (x-1) res=res/x*(x-1);
    return res;
}
int q_p(int x,int a,int mm)
{
    if (a <= 10000) return cc[a][mm];
    return (1ll*ex_c[a/10000][mm]*cc[a%10000][mm])%pp[mm];
}
int get_(int x,int num,int d)
{
    int t; 
    if (!num) if (x > pp[d]) return x%pp[d]; else return x;
    t = get_(x,num-1,d+1);
    double q = log(1.0/x*pp[d+1])/log(1.0*c)-num+1;
    if (q<=0) t += pp[d+1];
    return q_p(c,t,d);
}
void pre()
{
    int i,j;
    fo(i,0,10000)
        fo(j,0,k)
            {
                cc[i][j] = quick_power(c,i,pp[j]);
                int g = quick_power(c,10000,pp[j]);
                ex_c[i][j] = quick_power(g,i,pp[j]);
            }
    fo(i,1,n)
        if (a[i] == 0)
            {
                f[i][1] = 1;
                fo(j,2,k)
                    f[i][j] = get_(1,j-1,0);
            }   else
            {
                fo(j,1,k)
                    f[i][j] = get_(a[i],j,0);
            }   
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&p,&c);
    k = 0; pp[0] = p;
    while (pp[k]-1) {k++; pp[k] = phi(pp[k-1]);}
    k++; pp[k] = 1;
    fo(i,1,n) scanf("%d",&a[i]);
    pre();
    build(1,1,n);
    while (m--)
        {
            scanf("%d%d%d",&opt,&l,&r);
            if (opt == 0) change(1,1,n,l,r);
            if (opt == 1) printf("%d\n",query(1,1,n,l,r));
        }
    //fo(i,l,r) printf("%d ",query(1,1,n,i,i));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值