P3747 [六省联考 2017] 相逢是问候 题解
Description
Solution
不打算详细写了,简单写写做题历程。
一看题,显然要用线段树维护,但是 \(c^{a_i}\) 这东西怎存??
于是看了一眼标签,发现 欧拉公式
这个东西,于是想到欧拉定理。
再联想到区间开方的操作只有前 \(\sqrt n\) 次有用。发现这东西只有前 \(\log\) 次操作有用,所以直接暴力维护即可。
另外 \(c^x\) 次方要提前预处理出来,不然会多一个 \(\log\)。
其他的见代码吧,有一点注释。
这道题的思路还是非常有启发性的。
Code
#include <bits/stdc++.h>
#define ll long long
#define ls rt << 1
#define rs rt << 1 | 1
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 5e4 + 10;
int n, m, mod, c;
int a[N][60];
namespace Prework{
int c1[60][1 << 15], c2[60][1 << 15], phi[60];
int totp = 0;
inline int add(int x) {return x >= mod ? x -= mod : x;}
inline int times(ll x, int mod) {return x >= mod ? (x % mod + mod) : x;}
inline int power(int x, int i) {return times(1ll * c1[i][x & ((1 << 15) - 1)] * c2[i][x >> 15], phi[i]);}
//单点计算 phi
inline int calc_phi(int x){
int res = x;
for(int i = 2; i * i <= x; ++i){
if(x % i == 0){
res = res / i * (i - 1);
while(x % i ==0) x /= i;
}
}
if(x > 1) res = res / x * (x - 1);
return res;
}
//预处理 (c^x) % phi[i],分成两半(更快),对于每个 phi[i] 都要预处理
inline void calc_c(){
for(int i = 0; i <= totp; ++i){
c1[i][0] = c2[i][0] = 1;
for(int j = 1; j < (1 << 15); ++j)
c1[i][j] = times(1ll * c1[i][j - 1] * c, phi[i]);
c2[i][1] = times(1ll * c1[i][(1 << 15) - 1] * c, phi[i]);
for(int j = 2; j < (1 << 15); ++j)
c2[i][j] = times(1ll * c2[i][j - 1] * c2[i][1], phi[i]);
}
}
//预处理出 a[i] 每次操作之后数是多少
inline int calc(int x, int cnt, int i){
if(!cnt) return times(x, phi[i]);
if(i == totp) return c ? 1 : 0;
return power(calc(x, cnt - 1, i + 1), i);
}
//同上
inline void prework(){
phi[0] = mod;
while(phi[totp] > 1) totp++, phi[totp] = calc_phi(phi[totp - 1]);
calc_c();
for(int i = 1; i <= n; ++i){
a[i][0] = read();
for(int j = 1; j <= totp + 1; ++j){
a[i][j] = calc(a[i][0], j, 0) % mod;
}
a[i][0] %= mod;
}
}
}
using namespace Prework;
namespace Segment_Tree{
int sum[N << 2], mins[N << 2];//sum 记录区间和,mins 记录修改次数
inline void pushup(int rt){
mins[rt] = min(mins[ls], mins[rs]);
sum[rt] = add(sum[ls] + sum[rs]);
}
inline void build(int l, int r, int rt){
if(l == r){
sum[rt] = a[l][0];
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(rt);
}
inline void update(int L, int R, int l, int r, int rt){
if(mins[rt] > totp) return;
if(l == r) return sum[rt] = a[l][++mins[rt]], void();
int mid = (l + r) >> 1;
if(L <= mid) update(L, R, l, mid, ls);
if(R > mid) update(L, R, mid + 1, r ,rs);
pushup(rt);
}
inline int query(int L, int R, int l, int r, int rt){
if(L <= l && r <= R) return sum[rt];
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res = add(res + query(L, R, l, mid, ls));
if(R > mid) res = add(res + query(L, R, mid + 1, r, rs));
return res;
}
}
using namespace Segment_Tree;
signed main(){
n = read(), m = read(), mod = read(), c = read();
prework();
build(1, n, 1);
while(m--){
int op = read(), l = read(), r = read();
if(!op) update(l, r, 1, n, 1);
else write(query(l, r, 1, n, 1)), puts("");
}
return 0;
}
\[\_EOF\_
\]