• [ural 2124]. Algebra on Segment


    题意

    给出一个模(p)(p)是素数)意义下的序列,支持两种操作:

    1. 区间乘一个数;
    2. 询问一个区间的元素构成的群的大小。

    保证序列中的数时时刻刻不为(0)

    题解

    一道好题……sb了好久。
    第一个想法显然是先找到一个原根(g),再取指标进行运算。
    然后对于一个区间(g ^ {k_l}, g ^ {k_{l + 1}}, ldots, g ^ {k_r}),生成群的生成元(omega)(g ^ {(gcd{k_l, k_{l + 1}, ldots, k_r, p - 1})}),则群的大小为(frac{p - 1}{ind_g(omega)})
    看这个(gcd),因为要区间修改,所以要维护原序列和原序列的差分两个数组,因为

    [gcd(x_1, x_2, ldots, x_n) = gcd(x_1, x_2 - x_1, ldots, x_n - x_{n - 1}) ]

    这样便把区间修改变成单点修改。
    复杂度是(mathcal O((n + q) sqrt {(n + q) p} + q log ^ 2 n)),因为要bsgs求指标(我tmbsgs块大小还写挂了)。
    然而这样是过不了的。那咋整啊?
    XZA说指标的信息有冗余,实际上只需要求阶即可(求阶是(mathcal O(log ^ 2 p))的)。然而当时并没有理解这句话的意思,尽管感性理解好像是这回事。
    考虑直接用阶做。
    假设我们已经知道了区间([l, r])中每个元素的阶,那么如何求其生成群的大小?考虑两两合并。
    对于一个阶为(a)的元素和一个阶为(b)的元素,其生成的群可分别表示为(<g ^ {frac{p - 1}{a}}>, <g ^ {frac{p - 1}{b}}>)
    那么合并之后,群可表示为(<g ^ {gcd{(frac{p - 1}{a}}, frac{p - 1}{b})}>)。则新的群的大小为(frac{p - 1}{gcd{(frac{p - 1}{a}}, frac{p - 1}{b})})
    (这其中用到了一个很基础的定理:循环群的大小等于其生成元的阶。)
    简单运算一下,发现合并后群的大小就等于( ext{lcm} (a, b))。所以,这要求维护一个区间( ext{lcm})。但是区间( ext{lcm})也许并没有带区间乘的优秀做法。
    实际上,我们可以维护一个商分,即(a_i' = a_i * {a_{i - 1}} ^ {-1})。这样区间乘就变成了单点乘。
    根据这题的询问是群,则((a_l, a_{l + 1} * {a_l} ^ {-1}, ldots, a_r * {a_{r - 1}} ^ {-1}))形成的群和((a_l, a_{l + 1}, ldots, a_r))形成的群并没有区别,所以维护商分完全可行。
    因此,大概只需要一棵线段树维护原序列,一棵线段树维护商分序列的每个元素的阶的区间( ext{lcm})即可。
    复杂度(mathcal O((n + q) log ^ 2 n))。常数极大。(以上(log n)(log p)混用了,明白就好)

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    using namespace std;
    typedef pair <int, int> pii;
    const int N = 1e5 + 5;
    int mod, _mod, n, q;
    int a[N], g[N << 2], v[N << 2];
    vector <pii> pf;
    int lcm (int x, int y) {
    	return x / __gcd(x, y) * y;
    }
    int power (int x, int y, int mod) {
    	int ret = 1;
    	for ( ; y; y >>= 1, x = 1ll * x * x % mod) {
    		if (y & 1) {
    			ret = 1ll * ret * x % mod;
    		}
    	}
    	return ret;
    }
    void factorize (int x) {
    	pf.clear();
    	int t = sqrt(x + 1);
    	for (int i = 2, c; i <= t; ++i) {
    		if (x % i == 0) {
    			c = 0;
    			for ( ; x % i == 0; x /= i, ++c);
    			pf.push_back(make_pair(i, c));
    		}
    	}
    	if (x > 1) {
    		pf.push_back(make_pair(x, 1));
    	}
    }
    int ord (int x) {
    	int ret = _mod;
    	for (auto q : pf) {
    		for (int i = 1; i <= q.se; ++i) {
    			if (power(x, ret / q.fi, mod) == 1) {
    				ret /= q.fi;
    			} else {
    				break;
    			}
    		}
    	}
    	return ret;
    }
    void build (int o, int l, int r) {
    	v[o] = 1;
    	if (l == r) {
    		g[o] = v[o] = a[l];
    		if (l) {
    			g[o] = 1ll * g[o] * power(a[l - 1], mod - 2, mod) % mod;
    		}
    		g[o] = ord(g[o]);
    		return;
    	}
    	int mid = (l + r) >> 1;
    	build(o << 1, l, mid), build(o << 1 | 1, mid + 1, r);
    	g[o] = lcm(g[o << 1], g[o << 1 | 1]);
    }
    void pushdown (int o) {
    	if (v[o] != 1) {
    		v[o << 1] = (1ll * v[o << 1] * v[o]) % mod;
    		v[o << 1 | 1] = (1ll * v[o << 1 | 1] * v[o]) % mod;
    		v[o] = 1;
    	}
    }
    int query (int o, int l, int r, int x) {
    	if (l == r) {
    		return v[o];
    	}
    	pushdown(o);
    	int mid = (l + r) >> 1;
    	if (x <= mid) {
    		return query(o << 1, l, mid, x);
    	} else {
    		return query(o << 1 | 1, mid + 1, r, x);
    	}
    }
    void modify (int o, int l, int r, int x, int v) {
    	if (l == r) {
    		g[o] = 1ll * query(1, 1, n, l) * power(query(1, 1, n, l - 1), mod - 2, mod) % mod;
    		g[o] = ord(g[o]);
    		return;
    	}
    	int mid = (l + r) >> 1;
    	if (x <= mid) {
    		modify(o << 1, l, mid, x, v);
    	} else {
    		modify(o << 1 | 1, mid + 1, r, x, v);
    	}
    	g[o] = lcm(g[o << 1], g[o << 1 | 1]);
    }
    void modify (int o, int l, int r, int x, int y, int z) {
    	if (x <= l && r <= y) {
    		v[o] = 1ll * v[o] * z % mod;
    		return;
    	}
    	pushdown(o);
    	int mid = (l + r) >> 1;
    	if (x <= mid) {
    		modify(o << 1, l, mid, x, y, z);
    	}
    	if (y > mid) {
    		modify(o << 1 | 1, mid + 1, r, x, y, z);
    	}
    }
    int query (int o, int l, int r, int x, int y) {
    	if (x <= l && r <= y) {
    		return g[o];
    	}
    	int mid = (l + r) >> 1, ret = 1;
    	if (x <= mid) {
    		ret = lcm(ret, query(o << 1, l, mid, x, y));
    	}
    	if (y > mid) {
    		ret = lcm(ret, query(o << 1 | 1, mid + 1, r, x, y));
    	}
    	return ret;
    }
    int read () {
    	int x = 0; char ch = getchar();
    	for ( ; !isdigit(ch); ch = getchar());
    	for ( ; isdigit(ch); ch = getchar()) {
    		x = x * 10 + ch - '0';
    	}
    	return x;
    }
    void write (int x) {
    	if (x >= 10) {
    		write(x / 10);
    	}
    	putchar('0' + x % 10);
    }
    int main () {
    	mod = read(), n = read(), q = read();
    	factorize(_mod = mod - 1);
    	for (int i = 1; i <= n; ++i) {
    		a[i] = read();
    	}
    	build(1, 1, n);
    	for (int i = 1, o, l, r, x; i <= q; ++i) {
    		o = read(), l = read(), r = read();
    		if (o == 1) {
    			x = read();
    			modify(1, 1, n, l, r, x);
    			modify(1, 1, n, l, x);
    			if (r < n) {
    				modify(1, 1, n, r + 1, -x);
    			}
    		} else {
    			x = ord(query(1, 1, n, l));
    			if (l < r) {
    				x = lcm(x, query(1, 1, n, l + 1, r));
    			}
    			write(x), putchar('
    ');
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    【EXCEL】乱数関数集合
    PHP 获取当前时间前52周 12个月 4个季度
    python 清理没有过期时间的redis
    yii2 使用mongo查询(包含like查询)
    crontab 时间详解
    安装 cronsun
    php的加密&解密 (压缩数据) gzcompress & gzuncompress
    三数之和
    贪心算法解决集合覆盖问题
    KMP算法实现字符串匹配
  • 原文地址:https://www.cnblogs.com/psimonw/p/11790321.html
Copyright © 2020-2023  润新知