• LOJ #2142. 「SHOI2017」相逢是问候(欧拉函数 + 线段树)


    题意

    给出一个长度为 (n) 的序列 ({a_i}) 以及一个数 (p) ,现在有 (m) 次操作,每次操作将 ([l, r]) 区间内的 (a_i) 变成 (c^{a_i})

    或者询问 ([l, r]) 之间所有 (a_i) 的和对 (p) 取模的结果 。

    (n, m le 5 imes 10^4, p le 2^{14})

    题解

    考虑欧拉降幂(扩展欧拉定理),不会的可以看 这篇博客

    然后对于这些不断叠加的指数,有如下式子

    [egin{align} & quad c^{c^x} &pmod p \ & equiv c^{{c^x} mod varphi(p) + varphi(p)} &pmod p \ & equiv c^{{c^{x mod varphi(varphi(p)) + varphi(varphi(p)) }} mod varphi(p) + varphi(p)} &pmod p end{align} ]

    我们发现多次操作后 (varphi(...varphi(p)) = 1) 时,最高层就 (mod) 变成 (0) ,然后结果以后就不会改变了。

    这个次数约是 (O(log p)) 次的,我们就有个很直观的想法。

    考虑预处理每个数进行多次操作后变成的数,然后每次暴力改需要改的数。

    然后对于线段树每个区间,维护这个区间中的数改变次数的最小值,如果最小值 (ge) 当前的 (varphi) 的层数,直接退出即可。


    至于预处理十分的麻烦,不仅快速幂需要考虑是否 (ge varphi(p)) ,而且每次乘法都需要考虑这个qwq

    然后为了降低复杂度,我们在预处理的时候,对于底数 (c) 求一个幂次对于 (p) 模的值,可以利用大步小步预处理也就是 (sqrt p) 打个表,然后最后复杂度就可以做到 (O(sqrt p log^2 p + n (log n + log p)) log p)) 了。

    总结

    对于不断开方,或者与幂次有关的题,考虑欧拉扩展定理就行啦 qwq 因为是不超过 (O(log p)) 层的。

    代码

    具体看看代码实现qwq

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << (x) << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    
    using namespace std;
    
    typedef long long ll;
    
    template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
    template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
        int x(0), sgn(1); char ch(getchar());
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * sgn;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("2142.in", "r", stdin);
    	freopen ("2142.out", "w", stdout);
    #endif
    }
    
    const int N = 5e4 + 1e3;
    
    inline int phi(int x) {
    	int res = x;
    	For (i, 2, sqrt(x + .5)) if (!(x % i)) {
    		while (!(x % i)) x /= i;
    		res = res / i * (i - 1);
    	}
    	if (x > 1) res = res / x * (x - 1);
    	return res;
    }
    
    inline int fpm(int x, int power, int mod) {
    	int res = 1;
    	for (; power; power >>= 1, x = 1ll * x * x % mod + (1ll * x * x >= mod) * mod)
    		if (power & 1) res = 1ll * res * x % mod + (1ll * res * x >= mod) * mod;
    	return res;
    }
    
    int n, m, Mod, c;
    
    int a[N], Phi[33], val[N][33], lim = 0;
    
    const int Block = 1 << 14, All = Block - 1;
    int Pow1[N][33], Pow2[N][33];
    
    void Math_Init() {
    	for (Phi[0] = Mod; Phi[lim] > 1; ++ lim) 
    		Phi[lim + 1] = phi(Phi[lim]);
    	Phi[++ lim] = 1;
    
    	For (j, 1, lim) {
    		int cur = fpm(c, Block, Phi[j]);
    		Pow1[0][j] = Pow2[0][j] = 1;
    		For (i, 1, Block - 1) {
    			Pow1[i][j] = 1ll * Pow1[i - 1][j] * cur % Phi[j] + (1ll * Pow1[i - 1][j] * cur >= Phi[j]) * Phi[j];
    			Pow2[i][j] = 1ll * Pow2[i - 1][j] * c % Phi[j] + (1ll * Pow2[i - 1][j] * c >= Phi[j]) * Phi[j];
    		}
    	}
    
    	For (i, 1, n) {
    		val[i][0] = a[i];
    		For (j, 1, lim) {
    			int cur = a[i] % Phi[j] + (a[i] >= Phi[j]) * Phi[j];
    			Fordown (k, j - 1, 1) {
    				cur = 1ll * Pow1[cur / Block][k] * Pow2[cur & All][k] % Phi[k] 
    					+ (1ll * Pow1[cur / Block][k] * Pow2[cur & All][k] >= Phi[k]) * Phi[k];
    			}
    			val[i][j] = fpm(c, cur, Mod) % Mod;
    		}
    	}
    }
    
    inline int Plus(int a, int b) {
    	return (a += b) >= Mod ? a - Mod : a;
    }
    
    #define lson o << 1, l, mid
    #define rson o << 1 | 1, mid + 1, r
    
    template<int Maxn>
    struct Segment_Tree {
    
    	int times[Maxn], sumv[Maxn];
    
    	inline void Push_Up(int o) {
    		sumv[o] = Plus(sumv[o << 1], sumv[o << 1 | 1]);
    		times[o] = min(times[o << 1], times[o << 1 | 1]);
    	}
    
    	void Build(int o, int l, int r) {
    		if (l == r) { sumv[o] = a[l] % Mod; return ; }
    		int mid = (l + r) >> 1;
    		Build(lson); Build(rson); Push_Up(o);
    	}
    
    	void Update(int o, int l, int r, int ul, int ur) {
    		if (times[o] >= lim) return ;
    		if (ul <= l && r <= ur) ++ times[o];
    		if (l == r) { sumv[o] = val[l][times[o]]; return ; }
    		int mid = (l + r) >> 1;
    		if (ul <= mid) Update(lson, ul, ur);
    		if (ur > mid) Update(rson, ul, ur); Push_Up(o);
    	}
    
    	int Query(int o, int l, int r, int ql, int qr) {
    		if (ql <= l && r <= qr) return sumv[o];
    		int mid = (l + r) >> 1;
    		if (qr <= mid) return Query(lson, ql, qr);
    		if (ql > mid) return Query(rson, ql, qr);
    		return Plus(Query(lson, ql, qr), Query(rson, ql, qr));
    	}
    
    };
    
    Segment_Tree<N << 2> T;
    
    int main () {
    
    	File();
    
    	n = read(); m = read(); Mod = read(); c = read();
    
    	For (i, 1, n) a[i] = read();
    
    	Math_Init(); T.Build(1, 1, n);
    
    	For (i, 1, m) {
    		int opt = read(), l = read(), r = read();
    		if (!opt)
    			T.Update(1, 1, n, l, r);
    		else
    			printf ("%d
    ", T.Query(1, 1, n, l, r));
    	}
    
    	return 0;
    
    }
    
  • 相关阅读:
    C++ <cstring> 里的一些常用函数
    Hadoop_第一次作业
    线性回归理解和应用例子
    条款28 :避免返回handles指向对象内部成分
    条款25 :尽可能延后变量定义式的出现时间
    条款21 :必须返回对象时,别妄想返回其reference
    条款16:成对使用new和delete时要采用相同的形式
    条款22 :将成员变量声明为private
    条款13:以对象管理资源
    条款12:复制对象时勿忘其每一个成分
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9757406.html
Copyright © 2020-2023  润新知