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;
}
posted @ 2021-12-23 23:23  Neal_lee  阅读(88)  评论(0编辑  收藏  举报