[题解] 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^b\equiv a^{b \%\varphi(p)+\varphi(p)}\) (mod p) ,  gcd(a, p) \(\neq\) 1 且 b > \(\varphi(p)\)
 
 证明如下:
 证明一 证明二
 (不过反正我是一个都没看懂...逃...)

 假设当前a[i]为x
 我们对其进行一次操作有:
 
 \(c^x\equiv 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^d\equiv 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\n", Getsum(l, r, 1, 1, n));
	}
}
posted @ 2017-07-20 21:46  lstttt  阅读(329)  评论(0编辑  收藏  举报