[题解] bzoj 4869 shoi 2017相逢是问候(线段树+欧拉定理)

- 传送门 -

 http://www.lydsy.com/JudgeOnline/problem.php?id=4869
 

# 4869: [Shoi2017]相逢是问候

Time Limit: 40 Sec  Memory Limit: 512 MB
Submit: 823  Solved: 250
[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

Sample Output

0
3
 

- 题意 -

 给定区间, 两种操作:
 0 l r : 将区间[l, r]内的元素 (a_i) 改为 (c^{a_i}) .
 1 l r : 求区间[l, r]内元素的总和.
  

- 思路 -

 首先,有欧几里得定理的扩展(欧几里得定理EXT):
 
 (a^bequiv a^{b \%varphi(p)+varphi(p)}) (mod p) ,  gcd(a, p) ( eq) 1 且 b > (varphi(p))
 
 证明如下:
 证明一 证明二
 (不过反正我是一个都没看懂...逃...)

 假设当前a[i]为x
 我们对其进行一次操作有:
 
 (c^xequiv c^{x \%varphi(p)+varphi(p)}) , x > $ varphi(p)$ (mod p) (就和上面一样)
 
 对其进行两次操作的话则变成了求模p意义下的 (c^{c^x})
 此时 c 的上标变成了(c^x) , 我们设 (d = c^x), 即((c^{c^x}) = (c^d)), 则有
 
 (c^dequiv c^{d \%varphi(p)+varphi(p)}) (mod p)
 
 即(c^{c^x}equiv c^{{c^x} \%varphi(p)+varphi(p)}) (mod p)
 
 这时我们发现右式的上标中出现了一个熟悉的东西
 
 ({c^x} \%varphi(p))
 
 把(varphi(p))视为标准式中的p, 我们又可以对其进行转化了
 
 先设 d = (varphi(p))
 
 ({c^x}equiv c^{x \%varphi(d)+varphi(d)}) (mod d)
 
 即({c^x}equiv c^{x \%varphi(varphi(p))+varphi(varphi(p))}) (mod (varphi (p)))
 
 综上:
 
 (c^{c^x}equiv {c^{{c^x} \%varphi(p)+varphi(p)} equiv c^{c^{x \%varphi(varphi(p))+varphi(varphi(p))}  +varphi(p)}}) (mod p)
 
 那么当我们操作多次时, $x %varphi(...(varphi(p))) $ 中多次对p取欧拉函数会变成1, 于是顶层的c的上标就会变成
 
 x%1 + 1 = 1
 
 式子大概长这样:(c^{...^{c^{x\%varphi(...(varphi(p)))+varphi(...(varphi(p)))}}})
            (uparrow)   (uparrow)
          这是0 这是1
 化简后: (c^{...^c})
 也就是说, 原式的顶层的上标一定是会先 %1 再 +1 然后变成1.
 我们将此时(化简后)c的个数称为有效层数.
 再进行一次操作的话, 相当于再增加一层c.
 此时第二层的情况就相当与之前的顶层的情况(因为自底层向上数,每一层的处理是一样的,与层数,上标无关), 于是顶层就没有用了, 因为它会被次层整个先 %1 再 +1.
 这样顶层就没了, 有效层数不变.
 
 似乎只要处理到(varphi(p) = 1) 就可以了, 那是不是只要展开到(varphi(2) = 1)呢?
 
 我们看初始值x=0的情况:
 
 展开到(varphi(2) = 1)时:
 
 (c^{...^{c^{0\%varphi(...(varphi(p)))+varphi(...(varphi(p)))}}}equiv c^{...^{(c^{0\%varphi (2)+varphi(2)})\%2}}equiv c^{...^{c\%2}})
 
 展开到(varphi(1) = 1)时:
 
 (c^{...^{c^{c^{0\%varphi(...(varphi(p)))+varphi(...(varphi(p)))}}}}equiv c^{...{c^{(c^{0\%varphi (1)+varphi(1)})\%1}}}equiv c^{...{c^{0\%varphi(2)}\%2}}equiv c^{...1\%2})
  (uparrow)
 省略的c是一样多的
 
 我们发现 c%2 不一定等于 1 , 所以此时需要再展开一层.
 
 细节见代码.

 PS :
 噫!
 加上写题解这题大概花了两天!
 (事实上是一直在看题解然而看不懂最后还是问了小伙伴)
 所以说有小伙伴的最好去问小伙伴!!!
 (神犇请自动忽略上一句话)
 可是还是没有看懂怎么搞掉快速幂的那个log, 也没有搞懂定理的证明...
 我还是太水...
 

- 代码 -

#include<cstdio>
#define ls rt << 1
#define rs rt << 1 | 1
using namespace std;

typedef long long ll;
const int M = 5e4 + 5;
const int N = 1e4 + 5;

struct segtree {
	int tag, s;
}tr[M<<2];

int a[M];
int pi[30]; //phi
int pri[N + 5]; //素数
int isp[N + 5];
int n, m, p, c, t, k;

template <typename T> T Min (T a, T b) { return a < b ? a : b; } 

int Phi(int x) {
	int ans = x;
	for (int i = 1; i <= k && pri[i] * pri[i] <= x; ++i) {
		if (x % pri[i] == 0)
			ans = ans / pri[i] * (pri[i] - 1);		
		while (x % pri[i] == 0)
			x /= pri[i];
	}
	if (x > 1) ans = ans / x * (x-1); 
	return ans;
}//朴素的欧拉函数求法

void Init() {
	for (int i = 2; i <= N; ++i) {
		if (!isp[i]) pri[++k] = i;
		for (int j = 1; j <= k; ++j) {
			if (i * pri[j] > N) break;
			isp[pri[j]*i] = 1;
			if (i % pri[j] == 0) break;
		}
	}//预处理出素数用来算欧拉
	pi[t] = p;
	while (pi[t] != 1) pi[++t] = Phi(pi[t-1]);
	pi[++t] = 1;//求我们要用到的欧拉函数
}

void Pushup(int rt) {
	tr[rt].s = (tr[ls].s + tr[rs].s) % p;
	tr[rt].tag = Min(tr[ls].tag, tr[rs].tag);
}

void Build(int rt, int l, int r) {
	if (l == r) {
		scanf("%d", &a[l]);
		a[l] %= p;
		tr[rt].s = a[l];
		return;
	}
	int mid = l + r >> 1;
	Build(ls, l, mid);
	Build(rs, mid + 1, r);
	Pushup(rt);
}

int QP(int a, int b, int mod, int &f) {
	int ans = 1;
	while (b) {
		if (b & 1) {
			if (1ll * ans * a >= mod) f = 1;
	  	ans = 1ll * ans * a % mod;
	  }
	  if (1ll * a * a >= mod) f = 1;
	  a = 1ll * a * a % mod;
	  b >>= 1;
	} //快速幂, 同时判断f, f含义见下
	return ans;
}

int C(int a, int x) {
	int tmp = a;
	if (tmp > pi[x]) tmp = tmp%pi[x] + pi[x];
	for (int i = x, f; i > 0; --i) {
		f = 0; tmp = QP(c, tmp, pi[i-1], f);
		if (f) tmp += pi[i-1]; //f用来判断某情况下是否要加pi[i-1](c^tmp是否不小于pi[i-1])
	}
	return tmp;
}

void Modify(int x, int y, int rt, int l, int r) {
	if (tr[rt].tag >= t) return; //该区间每个数都被操作了t次, 不再改变
	if (l == r) {
		tr[rt].s = C(a[l], ++tr[rt].tag) % p;
		return;
	}
	int mid = l + r >> 1;
	if (x <= mid) Modify(x, y, ls, l, mid);
	if (mid < y) Modify(x, y, rs, mid+1, r);
	Pushup(rt);
}

int Getsum(int x, int y, int rt, int l, int r) {
	if (x <= l && r <= y) return tr[rt].s % p;
	int mid = l + r >> 1;
	int ans = 0;
	if (x <= mid) ans = (ans + Getsum(x, y, ls, l, mid)) % p;
	if (mid < y) ans = (ans + Getsum(x, y, rs, mid+1, r)) % p;
	return ans;
}

int main() {
	scanf("%d%d%d%d", &n, &m, &p, &c);
	Init();
	Build(1, 1, n);
	int f, l, r;
	while (m --) {
		scanf("%d%d%d", &f, &l, &r);
		if (f == 0) Modify(l, r, 1, 1, n);
		else printf("%d
", Getsum(l, r, 1, 1, n));
	}
}
原文地址:https://www.cnblogs.com/Anding-16/p/7214685.html