bzoj4869 [Shoi2017]相逢是问候
4869: [Shoi2017]相逢是问候
Time Limit: 40 Sec Memory Limit: 512 MBSubmit: 1311 Solved: 470
[Submit][Status][Discuss]
Description
Informatikverbindetdichundmich.
信息将你我连结。B君希望以维护一个长度为n的数组,这个数组的下标为从1到n的正整数。一共有m个操作,可以
分为两种:0 l r表示将第l个到第r个数(al,al+1,...,ar)中的每一个数ai替换为c^ai,即c的ai次方,其中c是
输入的一个常数,也就是执行赋值ai=c^ai1 l r求第l个到第r个数的和,也就是输出:sigma(ai),l<=i<=rai因为
这个结果可能会很大,所以你只需要输出结果mod p的值即可。
Input
第一行有三个整数n,m,p,c,所有整数含义见问题描述。
接下来一行n个整数,表示a数组的初始值。
接下来m行,每行三个整数,其中第一个整数表示了操作的类型。
如果是0的话,表示这是一个修改操作,操作的参数为l,r。
如果是1的话,表示这是一个询问操作,操作的参数为l,r。
1 ≤ n ≤ 50000, 1 ≤ m ≤ 50000, 1 ≤ p ≤ 100000000, 0 < c <p, 0 ≤ ai < p
Output
对于每个询问操作,输出一行,包括一个整数表示答案mod p的值。
Sample Input
4 4 7 2
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3
Sample Output
0
3
3
HINT
鸣谢多名网友提供正确数据,已重测!
Source
分析:比较难的一道题.
如果做过bzoj3884,就会想到用欧拉定理来降幂. 和区间开根号一样,一个数操作有限次数后就会变成一个常数. 在它变成常数以前对它暴力修改即可. 为什么会变成一个常数呢? p --> phi(p) --> phi(phi(p)) ...... 最后一定会变成1. 不论什么数mod 1都等于0.
区间开根号可以用分块来做,那么这道题能不能用分块来做呢?显然是不行的,复杂度太高! 每个数会被暴力修改log次,每次修改需要对log个phi求快速幂,快速幂的复杂度也是log的,也就是说:将一个数修改到底的复杂度是O(log^3n)的,显然是不能接受的. 既然不能分块,用线段树做就好了.
令p通过不断取phi变成1的次数为cnt. 如果要修改一个区间[l,r],若当前区间的所有元素的最少操作次数≥cnt,这个区间的元素就不需要被修改了. 否则暴力修改.
一开始将每一层的phi给记录下来(模数). 如果要修改第i个元素,第i个元素已经被修改tot次了,那么就从第tot + 1层到1层逐层计算答案. 需要注意的是:扩展欧拉定理只能在幂次≥phi的时候才能使用,在快速幂的时候判断一下就好了.
注意:一定要展开phi(1) = 1那一层. p=3,c=2的话,整个序列只有一个数字,一开始是0,接着不断进行修改操作和查询操作。
错误的代码,会返回0->1->2->2->2->.... (p=3,c=2),但是事实上应该是0->1->2->1->1->....
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 50010; ll n,m,P,c,a[maxn],p[maxn],cnt,sum[maxn << 2],minn[maxn << 2]; ll phi(ll x) { ll res = x; for (ll i = 2; i <= sqrt(x); i++) { if (x % i == 0) { while (x % i == 0) x /= i; res = res / i * (i - 1); } } if (x > 1) res = res / x * (x - 1); return res; } void pre() { ll t = P; p[0] = P; while (t != 1) { p[++cnt] = phi(t); t = p[cnt]; } p[++cnt] = 1; } void pushup(ll o) { sum[o] = sum[o * 2] + sum[o * 2 + 1]; sum[o] %= p[0]; minn[o] = min(minn[o * 2],minn[o * 2 + 1]); } void build(ll o,ll l,ll r) { if (l == r) { sum[o] = a[l] % p[0]; return; } ll mid = (l + r) >> 1; build(o * 2,l,mid); build(o * 2 + 1,mid + 1,r); pushup(o); } ll qpow(ll a,ll b,ll pp,bool &flag) { flag = false; ll res = 1; while (b) { if (b & 1) { if (res * a >= pp) flag = true; res = (res * a) % pp; } if (b != 1 && a * a >= pp) flag = true; a = (a * a) % pp; b >>= 1; } return res; } ll calc(ll x,ll ph) { ll res = x; bool flag; if (res >= p[ph]) res = res % p[ph] + p[ph]; while (ph--) { x = res; res = qpow(c,x,p[ph],flag); if (flag) res += p[ph]; } //cout << res << endl; return res % p[0]; } void update(ll o,ll l,ll r,ll x,ll y) { if (minn[o] >= cnt) return; if (l == r) { minn[o]++; sum[o] = calc(a[l],minn[o]); return; } ll mid = (l + r) >> 1; if (x <= mid) update(o * 2,l,mid,x,y); if (y > mid) update(o * 2 + 1,mid + 1,r,x,y); pushup(o); } ll query(ll o,ll l,ll r,ll x,ll y) { if (x <= l && r <= y) return sum[o]; ll mid = (l + r) >> 1,res = 0; if (x <= mid) res += query(o * 2,l,mid,x,y); res %= p[0]; if (y > mid) res += query(o * 2 + 1,mid + 1,r,x,y); res %= p[0]; return res; } int main() { scanf("%lld%lld%lld%lld",&n,&m,&P,&c); for (ll i = 1; i <= n; i++) scanf("%lld",&a[i]); pre(); build(1,1,n); for (ll i = 1; i <= m; i++) { ll opt,l,r; scanf("%lld%lld%lld",&opt,&l,&r); if (opt == 0) update(1,1,n,l,r); else printf("%lld\n",query(1,1,n,l,r)); } return 0; }