Timus 2124. Algebra on Segment
题目链接
Timus 2124. Algebra on Segment
题目大意
给定一个质数 \(p\) 和长度为 \(n\) 的正整数序列 \(a\),对于所有 \(i\in[1,n]\) 满足 \(1\leq a_i<p\),你需要维护一下两种操作:
- 将区间 \([l,r]\) 中所有 \(a_i\) 乘上 \(x\)。保证 \(1\leq x<p\) 。
- 计算区间 \([l,r]\) 中所有 \(a_i\) 组成的集合在模 \(p\) 乘法下的生成子群大小。
\(1\leq n,q\leq 10^5\),\(2\leq p\leq 10^9\)
Note
一个集合 \(T\) 生成的群为最小的 \(S\) 满足 \(T\sube S\),并且 \((S,\times)\) 是一个群。即对乘法封闭且存在逆元和单位元。
法一
模意义下的乘法组合十分不直观,考虑通过取对数将乘法转为加法,即离散对数。令 \(g\) 为 \(p\) 的原根,则 \(x\) 的离散对数为最小的满足 \(g^i\equiv x\!\! \mod p\) 的最小正整数 \(i\) 。容易证明两者代数结构是同构的。
转化成加法以后,可以发现,对于 \([l,r]\) 形成的生成子群,元素之间的最小步长为 \(\gcd(a_l,a_{l+1},...,a_{r},p-1)\),于是生成子群的大小即为 \(\frac{p-1}{\gcd(a_l,a_{l+1},...,a_{r},p-1)}\)。现在便是一组「 区间加,求区间 \(\gcd\)」的操作,显然 \(\gcd(a_l,a_{l+1},...,a_r)=\gcd(a_l,a_{l+1}-a_l,...,a_r-a_{r-1})\),所以线段树维护差分数组 \(\gcd\),树状数组维护原序列即可。
回到离散对数部分,一般 \(BSGS\) 求指标是直接 \(O(\sqrt V)\) 分块的,注意到这里 \(p\) 和 \(n,q\) 不同阶,所以需调整块长。\(BSGS\) 通过设定常数 \(B\),将 \(a^x\equiv b\!\!\mod p\) 换成 \(a^{\alpha B}\equiv b\times a^\beta\!\!\mod p\) 的形式,枚举 \(\alpha\) 预处理左侧的值放到哈希表中,查询时枚举 \(\beta\) 检查是否存在 \(a^{\alpha B}\) 与其相等,从而这里的时间复杂度是 \(O(\frac{p}{B}+(n+q)B)\) 的, \(B=\sqrt{\frac{p}{n+q}}\) 时有最优复杂度 \(O(\sqrt{p(n+q)})\) 。
从而总时间复杂度 \(O(\sqrt{p(n+q)}+(n+q)\log^2 p)\)
Code
// a*b -> log(a)+log(b) with BSGS
// O(sqrt(p(n+q))+(n+q)log^2(p))
#include<iostream>
#include<vector>
#include<fstream>
#include<cstdio>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 101000
#define B 300 // sqrt(mod/(n+q))
#define SIZ 3340000 // mod/B
#define V 19260817
#define ll long long
#define lowbit(x) (x&-x)
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
return s*w;
}
int mod, g;
int n, q, a[N];
struct Hash{
int head[V], key[SIZ], val[SIZ], nxt[SIZ];
int cnt;
void insert(int x, int v){
++cnt, key[cnt] = x, val[cnt] = v;
nxt[cnt] = head[v%V], head[v%V] = cnt;
}
int find(int v){
int id = v%V, it = head[id];
while(it){
if(val[it] == v) return key[it];
it = nxt[it];
}
return 0;
}
} Hash;
ll qpow(ll a, int b){
ll ret = 1;
for(; b; b >>= 1){ if(b&1) (ret *= a) %= mod; (a *= a) %= mod; }
return ret;
}
struct BSGS{
void init(){
ll x = 1, t = qpow(g, B);
rep(i,1,mod/B+1) Hash.insert(i, (x *= t) %= mod);
}
int query(int n){
ll t = 1;
rep(i,1,B){
int x = Hash.find(n * ((t *= g) %= mod) % mod);
if(x) return (x*B - i) % (mod-1);
}
}
} BSGS;
void prework(){
vector<int> factor;
int x = mod-1;
for(int i = 2; i*i <= x; i++) if(x%i == 0){
factor.push_back(i);
while(x%i == 0) x /= i;
}
if(x > 1) factor.push_back(x);
rep(i,1,mod){
bool flag = true;
for(int k : factor) if(qpow(i, (mod-1)/k) == 1){ flag = false; break; }
if(flag){ g = i; return; }
}
}
struct Fenwick{
int t[N];
void update(int pos, int k){
while(pos <= n) (t[pos] += k) %= (mod-1), pos += lowbit(pos);
}
int get(int pos){
int ret = 0;
while(pos) (ret += t[pos]) %= (mod-1), pos -= lowbit(pos);
return ret;
}
} BIT;
int gcd(int a, int b){ return b ? gcd(b, a%b) : a; }
struct SegmentTree{
int t[N<<2];
void build(int x, int l, int r){
if(l == r){ t[x] = (a[l]+mod-1-a[l-1]) % (mod-1); return; }
int mid = (l+r)>>1;
build(x*2, l, mid), build(x*2+1, mid+1, r);
t[x] = gcd(t[x*2], t[x*2+1]);
}
void update(int x, int l, int r, int pos, int k){
if(l == r){ (t[x] += k) %= (mod-1); return; }
int mid = (l+r)>>1;
if(mid >= pos) update(x*2, l, mid, pos, k);
else update(x*2+1, mid+1, r, pos, k);
t[x] = gcd(t[x*2], t[x*2+1]);
}
int get(int x, int l, int r, int L, int R){
if(l >= L && r <= R) return t[x];
int mid = (l+r)>>1, ret = 0;
if(mid >= L) ret = gcd(ret, get(x*2, l, mid, L, R));
if(mid < R) ret = gcd(ret, get(x*2+1, mid+1, r, L, R));
return ret;
}
} T;
int main(){
mod = read(), n = read(), q = read();
prework(), BSGS.init();
rep(i,1,n){
a[i] = read(), a[i] = BSGS.query(a[i]);
BIT.update(i, a[i]-a[i-1]);
}
T.build(1, 1, n);
int type, l, r, x;
while(q--){
type = read(), l = read(), r = read();
if(type == 1){
x = read(), x = BSGS.query(x);
BIT.update(l, x), BIT.update(r+1, -x);
T.update(1, 1, n, l, x);
if(r < n) T.update(1, 1, n, r+1, -x);
} else{
x = gcd(BIT.get(l), mod-1);
if(l < r) x = gcd(x, T.get(1, 1, n, l+1, r));
printf("%d\n", abs((mod-1) / x));
}
}
return 0;
}
法二
注意到单一元素 \(x\) 的生成子群大小等于阶 \(\delta(x)\),考虑直接用阶做。
对于一个阶为 \(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})}=\text{lcm}(a,b)\),于是问题转化成了区间求 \(\text{lcm}\) 。
不过 \(\delta(ab)=\delta(a)\delta(b)\iff \gcd(\delta(a),\delta(b))=1\),题目不具备这样的性质,所以我们没法维护直接区间乘 \(x\)。转而考虑商分,记 \(a_i'=\frac{a_i}{a_{i-1}}\),这样在区间乘时,直接修改区间端点处的 \(\delta(x)\) 值即可。而 \(\{a_l,a_{l+1}',a_{l+1}',...,a_r'\}\) 的生成子群和 \(\{a_l,a_{l+1},a_{l+2},...,a_r\}\) 相同,所以这样是可行的。
对 \(x\) 求阶可以做到 \(O(\log^2p)\),对于 \(p\) 的所有质因数 \(q\)(指数大于 \(1\) 就重复多次),若 \(x^{\frac{p}{q}}\equiv 1\!\!\mod p\),则将 \(p\) 除去 \(q\),最终的值即为 \(\delta(x)\)。
时间复杂度 \(O((n+q)\log^2 p)\)
Code
// size = lcm{ord(a_i)}
// O((n+q)log^2(p))
#include<iostream>
#include<vector>
#include<fstream>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 101000
#define ll long long
#define lowbit(x) (x&-x)
using namespace std;
int mod;
int n, q, a[N];
vector<int> factor;
ll qpow(ll a, int b){
ll ret = 1;
for(; b; b >>= 1){ if(b&1) (ret *= a) %= mod; (a *= a) %= mod; }
return ret;
}
int gcd(int a, int b){ return b ? gcd(b, a%b) : a; }
int lcm(int a, int b){ return a / gcd(a, b) * b; }
void factorize(int x){
for(int i = 2; i*i <= x; i++) if(x%i == 0)
while(x%i == 0) factor.push_back(i), x /= i;
if(x > 1) factor.push_back(x);
}
int ord(int x){
int ret = mod-1;
for(int k : factor) if(qpow(x, ret/k) == 1) ret /= k;
return ret;
}
struct Fenwick{
ll t[N];
void init(){ rep(i,1,n) t[i] = 1; }
void update(int pos, int k){
while(pos <= n) (t[pos] *= k) %= mod, pos += lowbit(pos);
}
ll get(int pos){
ll ret = 1;
while(pos) (ret *= t[pos]) %= mod, pos -= lowbit(pos);
return ret;
}
} BIT;
struct SegmentTree{
ll t[N<<2];
void build(int x, int l, int r){
if(l == r){
t[x] = a[l];
if(l) (t[x] *= qpow(a[l-1], mod-2)) %= mod;
t[x] = ord(t[x]);
return;
}
int mid = (l+r)>>1;
build(x*2, l, mid), build(x*2+1, mid+1, r);
t[x] = lcm(t[x*2], t[x*2+1]);
}
void update(int x, int l, int r, int pos){
if(l == r){
t[x] = ord(BIT.get(l) * qpow(BIT.get(l-1), mod-2) % mod);
return;
}
int mid = (l+r)>>1;
if(mid >= pos) update(x*2, l, mid, pos);
else update(x*2+1, mid+1, r, pos);
t[x] = lcm(t[x*2], t[x*2+1]);
}
ll query(int x, int l, int r, int L, int R){
if(l >= L && r <= R) return t[x];
int mid = (l+r)>>1; ll ret = 1;
if(mid >= L) ret = lcm(ret, query(x*2, l, mid, L, R));
if(mid < R) ret = lcm(ret, query(x*2+1, mid+1, r, L, R));
return ret;
}
} T;
int main(){
ios::sync_with_stdio(false);
cin>>mod>>n>>q;
BIT.init();
rep(i,1,n) cin>>a[i], BIT.update(i, (i > 1 ? a[i] * qpow(a[i-1], mod-2) % mod : a[i]));
factorize(mod-1), T.build(1, 1, n);
int type, l, r, x;
while(q--){
cin>>type>>l>>r;
if(type == 1){
cin>>x;
BIT.update(l, x), BIT.update(r+1, qpow(x, mod-2));
T.update(1, 1, n, l);
if(r < n) T.update(1, 1, n, r+1);
} else{
int x = ord(BIT.get(l));
if(l < r) x = lcm(x, T.query(1, 1, n, l+1, r));
cout<< x <<endl;
}
}
return 0;
}