城中分支

城中分支

题目描述

“城市处于建设中......”

预言之子的你获得了一个拥有 n 个元素的数组。 天神赋予你 q 次操作:

  1. Modifylrx------对于每个 i (lir),将 ai 乘以 x
  2. Querylr------你需要回答 φ(i=lrai) 取模 109+7,其中 φ 表示欧拉函数。

正整数 n(表示为 φ(n))的欧拉函数是满足 gcd(n,x)=1 的整数 x (1xn) 的数量。

i=lrai 表示 al×al+1××ar

输入描述:

第一行包含两个整数 nq (1n4105,1q2105) — 数组中的元素数和查询数。

第二行包含 n 个整数 a1,a2,,an (1ai300) — 数组 a 的元素。

接下来是 q 行以语句中给出的格式描述查询。

Modifylrx (1lrn,1x300) —表示修改操作。

Querylr (1lrn) ——表示对欧拉函数值的查询操作。

数据保证至少有一个 Query 查询。

输出描述:

对于每个 Query 查询,打印其答案对 109+7 取模的结果。

示例1

输入

4 4
5 9 1 2
Query 3 3
Query 3 4
Modify 4 4 3
Query 4 4

输出

1
1
2

示例2

输入

1 1
4
Query 1 1

输出

2

 

解题思路

  代码巨长巨难写,卡了快 3 个小时,不是 TLE 就是 MLE。

  首先有欧拉函数公式 φ(x)=x(11P0)(11P1)(11Pk),其中 x=P0α0P1α1Pkαk。因此有 φ(x)=P0α0(11P0)P1α1(11P1)Pkαk(11Pk)=φ(P0α0)φ(P1α1)φ(Pkαk)

  显然需要用到线段树,但我们既不可以直接维护区间的乘积(显然爆 long long),又不可以对乘积取模(否则求不了欧拉函数)。注意到 aix 都不超过 300,而 300 内的质数只有 62 个,意味着乘积的结果均可以表示成 P0α0P1α1P61α61,因此我们可以转向去维护乘积结果的各个质因子的数量即 αi

  定义线段树节点信息:

struct Node {
    int l, r;
    array<LL, 62> s, sum;
};

  当需要给节点维护的整个区间乘上 x=P0α0P1α1P61α61 时,只需更新 sisi+αi(rl+1)sumisumi+αi (0i<62)sum 是懒标记)。

  查询时,我们累加询问区间内每个质因子的次数,用大小为 62 的数组 s 表示。乘积的欧拉函数就是 i=061[si>0]Pisi(11Pi)

  上述做法的时间复杂度为 O(nlogA+qlogn(logA+π(A)log(qlogA))),空间复杂度为 O(nπ(A))。然而实际上线段树所需要的内存空间为 1k+ MB!上述做法肯定过不了。

  用不了线段树然后我就投机取巧用分块,结果空间是变小了但 TLE。做法和上面类似,每个块维护各个质因子的数量,具体做法就不详述了。分块的时间复杂度为 O(nlogA+qn(logA+π(A)log(qlogA))),空间复杂度为 O(nπ(A)) 但常数比上面的小非常多。

  分块做法 TLE 代码如下:

查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 4e5 + 5, M = 305, B = 640, mod = 1e9 + 7;

int a[N][62];
int prime[M], mp[M], minp[M], cnt;
bool vis[M];
int id[N];
LL s[B][M], sum[B][M], c[M];

void get_prime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[cnt] = i;
            mp[i] = cnt++;
            minp[i] = i;
        }
        for (int j = 0; prime[j] * i <= n; j++) {
            vis[prime[j] * i] = true;
            minp[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) break;
        }
    }
}

int qmi(int a, LL k) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod;
        k >>= 1;
    }
    return ret;
}

void modify(int l, int r, int x) {
    if (id[l] == id[r]) {
        for (int i = l; i <= r; i++) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        
    }
    else {
        for (int i = l; id[i] == id[l]; i++) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        for (int i = r; id[i] == id[r]; i--) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        for (int i = id[l] + 1; i < id[r]; i++) {
            int t = x;
            while (t > 1) {
                s[i][mp[minp[t]]] += B;
                sum[i][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
    }
}

int query(int l, int r) {
    memset(c, 0, sizeof(c));
    if (id[l] == id[r]) {
        for (int i = l; i <= r; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
    }
    else {
        for (int i = l; id[i] == id[l]; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
        for (int i = r; id[i] == id[r]; i--) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
        for (int i = id[l] + 1; i < id[r]; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += s[i][j];
            }
        }
    }
    int ret = 1;
    for (int i = 0; i < cnt; i++) {
        if (c[i]) ret = ret * (prime[i] - 1ll) % mod * qmi(prime[i], c[i] - 1) % mod;
    }
    return ret;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    get_prime(M - 1);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        id[i] = (i - 1) / B + 1;
        while (x > 1) {
            a[i][mp[minp[x]]]++;
            s[id[i]][mp[minp[x]]]++;
            x /= minp[x];
        }
    }
    while (m--) {
        string s;
        cin >> s;
        if (s[0] == 'M') {
            int l, r, x;
            cin >> l >> r >> x;
            modify(l, r, x);
        }
        else {
            int l, r;
            cin >> l >> r;
            cout << query(l, r) << '\n';
        }
    }
    
    return 0;
}

  下面给出正解了,还是用到线段树。细想一下,最开始的线段树做法会 MLE,正是因为我们给每个节点都开了大小为 62 的数组去统计各个质因子的数量,而我们有必要去记录质因子的数量吗?回想欧拉函数 φ(x)=x(11P0)(11P1)(11Pk),我们只要分别知道对应区间乘积的结果(可以取模),以及乘积结果(不取模)所含有的质因子,就可以计算欧拉函数。可以发现我们并不需要统计各个质因子的数量,只需统计出现了哪些质因子即可,这个可以用一个 long long 变量来状态压缩统计。

  为此重新定义线段树节点信息:

struct Node {
    int l, r;
    int p, prod;
    LL s, sum;
};

  其中 p 是区间乘积取模后的结果,prod 是对应的区间乘懒标记。s 是状态压缩表示区间乘积(没取模)包含的质因子,sum 是对应的区间加懒标记。节点更新以及懒标记下传请参考代码,这里就不过多赘述了。

  AC 代码如下,时间复杂度为 O(nlogA+q(logn+π(A))),空间复杂度为 O(n)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 4e5 + 5, M = 305, B = 640, mod = 1e9 + 7;

int a[N][62];
int prime[M], mp[M], minp[M], cnt;
bool vis[M];
int id[N];
LL s[B][M], sum[B][M], c[M];

void get_prime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[cnt] = i;
            mp[i] = cnt++;
            minp[i] = i;
        }
        for (int j = 0; prime[j] * i <= n; j++) {
            vis[prime[j] * i] = true;
            minp[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) break;
        }
    }
}

int qmi(int a, LL k) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod;
        k >>= 1;
    }
    return ret;
}

void modify(int l, int r, int x) {
    if (id[l] == id[r]) {
        for (int i = l; i <= r; i++) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        
    }
    else {
        for (int i = l; id[i] == id[l]; i++) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        for (int i = r; id[i] == id[r]; i--) {
            int t = x;
            while (t > 1) {
                a[i][mp[minp[t]]]++;
                s[id[i]][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
        for (int i = id[l] + 1; i < id[r]; i++) {
            int t = x;
            while (t > 1) {
                s[i][mp[minp[t]]] += B;
                sum[i][mp[minp[t]]]++;
                t /= minp[t];
            }
        }
    }
}

int query(int l, int r) {
    memset(c, 0, sizeof(c));
    if (id[l] == id[r]) {
        for (int i = l; i <= r; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
    }
    else {
        for (int i = l; id[i] == id[l]; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
        for (int i = r; id[i] == id[r]; i--) {
            for (int j = 0; j < cnt; j++) {
                c[j] += a[i][j] + sum[id[i]][j];
            }
        }
        for (int i = id[l] + 1; i < id[r]; i++) {
            for (int j = 0; j < cnt; j++) {
                c[j] += s[i][j];
            }
        }
    }
    int ret = 1;
    for (int i = 0; i < cnt; i++) {
        if (c[i]) ret = ret * (prime[i] - 1ll) % mod * qmi(prime[i], c[i] - 1) % mod;
    }
    return ret;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    get_prime(M - 1);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        id[i] = (i - 1) / B + 1;
        while (x > 1) {
            a[i][mp[minp[x]]]++;
            s[id[i]][mp[minp[x]]]++;
            x /= minp[x];
        }
    }
    while (m--) {
        string s;
        cin >> s;
        if (s[0] == 'M') {
            int l, r, x;
            cin >> l >> r >> x;
            modify(l, r, x);
        }
        else {
            int l, r;
            cin >> l >> r;
            cout << query(l, r) << '\n';
        }
    }
    
    return 0;
}

 

参考资料

  沈阳化工大学第十一届程序设计竞赛专业组题解:https://ac.nowcoder.com/discuss/1408217

  thisislike_fan 提交的代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=71790420

posted @   onlyblues  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-10-07 D. Prefix Purchase
Web Analytics
点击右上角即可分享
微信分享提示