Codeforces Round #705 (Div. 2)-D. GCD of an Array,数论+数据结构(好题)

题目大意

给你一个长度为 n n n的序列。 q q q次操作。每次操作 a i : = a i ∗ x a_i:=a_i*x ai:=aix。每次操作完问你 g c d ( a 1 , . . . , a n ) gcd(a_1,...,a_n) gcd(a1,...,an).

n , q ≤ 2 e 5 n,q \leq 2e5 n,q2e5

题目思路

这题的关键点有:

1.gcd可以按素数独立计算贡献

2.所有数所产生的素数个数不会很多,在logn级别。

1.离线做法 + 线段树

分析:没有要求强制在线,所以我们可以考虑离线做。我们将初始化也看作是 n n n次操作。那么一开始序列就是全1序列。

做法:每次操作可以被描述为: ( 时 刻 , 位 置 , 相 乘 的 因 子 x ) (时刻,位置,相乘的因子x) (,,x).然后我们对每个质因子单独考虑:

对这些数都质因分解.开 素 数 素数 个vector,里面就存上面的结构体。对时间排序.

接下来就是按每个质因子进行模拟。将乘法转化成加法。问题就变成了:单点加,查询整体最小值。直接线段树即可。每次查询完后,要与上一个修改时刻的最小值进行比较。如果没有变化,则代表没有贡献。所以开一个ans数组,来记录第 t i ti ti个时刻的贡献(类似打标记)。最后对其求一个前缀积即可。每次计算完一个质数的贡献后记得对线段树撤销所有修改.

时间复杂度:

O ( n n + n π ( a i ) l o g n ) O(n\sqrt{n}+n\pi(a_i)logn) O(nn +nπ(ai)logn).

其中 n n n\sqrt{n} nn 来自于质因分解,
π ( a i ) \pi(a_i) π(ai) a i a_i ai的不同的质数个数. ≤ 6 \leq 6 6.
l o g n logn logn为对线段树的操作的复杂度.

代码见代码仓库1-1

2.在线做法

2.1:动态开点线段树

分析:考虑一种暴力的方法:开 1 e 5 1e5 1e5个线段树。每次新增一个数之后,将其质因分解,将每个质数以及其指数大小插入到线段树中。维护一个 a n s ans ans.每次还是更新贡献。显然空间复杂度不够。

优化:我们利用动态开点线段树对空间进行复用即可。严格讲空间复杂度到 n l o g n l o g n nlognlogn nlognlogn.但是不难发现由于大小的限制和素数的限制远达不到上述上界。实测线段树空间开到 1 e 7 1e7 1e7即可.

时间复杂度: O ( n n + n π ( n ) l o g n ) O(n\sqrt{n}+n\pi(n)logn) O(nn +nπ(n)logn).

类似思路的题目:CF979D

代码见代码仓库2-1

2.2 暴力开multiset+map

分析:考虑一种暴力想法:对每个点开一个 1 e 5 1e5 1e5 m a p map map. m p [ i ] [ j ] mp[i][j] mp[i][j]就代表第 i i i个位置因数 j j j的出现次数.然后对每个值开一个 m u l t i s e t multiset multiset. s e t [ i ] set[i] set[i]代表当前时刻所有含有值 i i i的数其指数大小.每次查询全局最小值是非常方便的.

之后暴力模拟即可。对 m u l t i s e t multiset multiset的操作就是先删除掉原来的值,然后再更改再插入。再查询最小值。

代码见代码仓库2-2(官方题解代码)

心得

这个题目收获还是挺多的。

1.get到动态开点线段树的一种用法.更进一步理解了线段树的本质。并且发现之前的板子有问题。更新了一种更好理解,空间更小的板子.

2.codeforces的题目还是不要想的太复杂了。很多时候先想怎么暴力的空间换时间

3.单点修改+查询全局最值 优先考虑 s e t / m u l t i s e t set/multiset set/multiset

代码仓库

1.1:离线+线段树做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define tl (t<<1)
#define tr (t<<1|1)
#define mid ((r + l) >> 1)
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
ll ksm (ll a , ll b){
    
     ll ans = 1 , base = a;
while (b){
    
    if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
struct Node
{
    
    
    int ti , id , val;
};
int n , q;
vector<Node> a[maxn];
ll ans[maxn * 2];
const ll inf = 1e18;
int sum[maxn<<2];
void pushup(int t)
{
    
    
    sum[t] = min(sum[tl] , sum[tr]);
}

void add(int l , int r , int t , int pos , int c)
{
    
    
    if(l == r)
    {
    
    
        sum[t] += c;
        return ;
    }
    if(pos <= mid)
        add(l,mid,tl,pos,c);
    else
        add(mid+1,r,tr,pos,c);
    pushup(t);
    return ;
}
ll ask(int L,int R,int l,int r,int t)
{
    
    
    if(L <= l && r <= R)
    {
    
    
        return sum[t];
    }
    ll ans=inf;
    if(L <= mid)
        ans = min(ans , ask(L,R,l,mid,tl));
    if(R > mid)
        ans = min(ans , ask(L,R,mid+1,r,tr));
    return ans;
}

int main()
{
    
    
    ios::sync_with_stdio(false);
    cin >> n >> q;
    for (int i = 0 ; i <= n + q ; i++){
    
    
        ans[i] = 1;
    }
    for (int i = 1 ; i <= n ; i++){
    
    
        int x;cin >> x;
        int g = x;
        for (int j = 2 ; j * j <= g ; j++){
    
    
            if (g % j) continue;
            int cnt = 0;
            while (g % j == 0){
    
    
                g /= j;
                cnt++;
            }
            a[j].pb({
    
    i , i , cnt});
        }
        if (g != 1)
            a[g].pb({
    
    i , i , 1});
    }
    for (int i = n + 1 ; i <= n + q ; i++){
    
    
        int id , x; cin >> id >> x;
        int g = x;
        for (int j = 2 ; j * j <= g ; j++){
    
    
            if (g % j) continue;
            int cnt = 0;
            while (g % j == 0){
    
    
                g /= j;
                cnt++;
            }
            a[j].pb({
    
    i , id , cnt});
        }
        if (g != 1)
            a[g].pb({
    
    i , id , 1});
    }
    for (int i = 2 ; i < maxn ; i++){
    
    
        if (a[i].size() == 0) continue;
        int last = 0;
        for (auto & g : a[i]){
    
    
            int ti = g.ti , id = g.id , val = g.val;
            add(1 , n , 1 , id , val);
            if (last == sum[1]) continue;
            ans[ti] = (ans[ti] * ksm(i , sum[1] - last))%mod;
            last = sum[1];
        }
        // 撤销贡献
        for (auto & g : a[i]){
    
    
            int id = g.id , val = g.val;
            add(1 , n , 1 , id , -val);
        }
    }
    for (int i = 1 ; i <= n + q ; i++){
    
    
        ans[i] = ans[i - 1] * ans[i] % mod;
    }
    for (int i = n + 1 ; i <= n + q ; i++){
    
    
        cout << ans[i] << endl;
    }
    return 0;
}
2.1:动态开点线段树做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define mid ((l + r) >> 1)
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
ll ksm (ll a , ll b){
    
     ll ans = 1 , base = a;
while (b){
    
    if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
int n , q;
const int maxm = 1e7 + 2e5 + 5;
int ls[maxm] , rs[maxm] , sum[maxm] , rt[maxn] , tot;
void pushup (int t)
{
    
    
    int tl = ls[t] , tr = rs[t];
    sum[t] = min(sum[tl] , sum[tr]);
}
void add (int &t , int l , int r , int p , int c){
    
    
    if (!t) t = ++tot;
    if (l == r) {
    
    
        sum[t] += c;
        return ;
    }
    if (p <= mid) add(ls[t] , l , mid , p , c);
    else add(rs[t] , mid + 1 , r , p , c);
    pushup(t);
    return ;
}
int main()
{
    
    
    ios::sync_with_stdio(false);
    cin >> n >> q;
    ll ans = 1;
    for (int i = 1 ; i <= n ; i++){
    
    
        int x;cin >> x;
        int g = x;
        for (int j = 2 ; j * j <= g ; j++){
    
    
            if (g % j) continue;
            int cnt = 0;
            while (g % j == 0){
    
    
                g /= j;
                cnt++;
            }
            ll f = sum[rt[j]];
            add(rt[j] , 1 , n , i , cnt);
            ans = (ans * ksm(j , sum[rt[j]] - f))%mod;
        }
        if (g != 1) {
    
    
            ll f = sum[rt[g]];
            add(rt[g] , 1 , n , i , 1);
            ans = (ans * ksm(g , sum[rt[g]] - f))%mod;
        }
    }

    for (int i = 1 ; i <= q ; i++){
    
    
        int id , x;cin >> id >> x;
        int g = x;
        for (int j = 2 ; j * j <= g ; j++){
    
    
            if (g % j) continue;
            int cnt = 0;
            while (g % j == 0){
    
    
                g /= j;
                cnt++;
            }
            ll f = sum[rt[j]];
            add(rt[j] , 1 , n , id , cnt);
            ans = (ans * ksm(j , sum[rt[j]] - f))%mod;
        }
        if (g != 1) {
    
    
            ll f = sum[rt[g]];
            add(rt[g] , 1 , n , id , 1);
            ans = (ans * ksm(g , sum[rt[g]] - f))%mod;
        }
        cout << ans << endl;
    }

    return 0;
}

2.2:暴力开map+multiset做法
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int const maxn = 2e5 + 5, max_val = 2e5 + 5;
ll mod = 1e9 + 7, ans = 1;
int nxt[max_val], n;
multiset <int> cnt[max_val];
map <int, int> cnt_divisor[maxn];

void add(int i, int x) {
    
    
    while (x != 1) {
    
    
        int div = nxt[x], add = 0;
        while (nxt[x] == div) add++, x = x / nxt[x];

        int lst = cnt_divisor[i][div];
        cnt_divisor[i][div] += add;
        int lst_min = 0;
        if ((int)cnt[div].size() == n) {
    
    
            lst_min = (*cnt[div].begin());
        }
        if (lst != 0) {
    
    
            cnt[div].erase(cnt[div].find(lst));
        }
        cnt[div].insert(lst + add);
        if ((int)cnt[div].size() == n) {
    
    
            for (int j = lst_min + 1; j <= (*cnt[div].begin()); ++j) {
    
    
                ans = ans * (ll)div % mod;
            }
        }
    }
}

main() {
    
    
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    int q, l, x;
    cin >> n >> q;

    for (int i = 2; i < maxn; ++i) {
    
    
        if (nxt[i] == 0) {
    
    
            nxt[i] = i;
            if (i > 10000) continue;
            for (int j = i * i; j < maxn; j += i) {
    
    
                if (nxt[j] == 0) nxt[j] = i;
            }
        }
    }

    for (int i = 1; i <= n; ++i) {
    
    
        cin >> x;
        add(i, x);
    }

    for (int i = 1; i <= q; ++i) {
    
    
        cin >> l >> x;
        add(l, x);
        cout << ans << '\n';
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35577488/article/details/114535914
今日推荐