• [题解] 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));
    	}
    }
    
  • 相关阅读:
    [c++]在类中定义常量的几个做法
    VC6中使用高版本系统API的方法
    Delphi编程中实现窗口分割
    Win32 SDK窗口程序代码(含详细注释)
    [c++]在C++中定义常量的两种方法的比较
    VC6里的_WIN32_WINNT宏
    [VC]自己实现TRACE功能
    [delphi]保证程序只运行一个实例
    转载:C# 设置文件夹权限(代码简单)
    VC:动态链接库
  • 原文地址:https://www.cnblogs.com/Anding-16/p/7214685.html
Copyright © 2020-2023  润新知