2021CCPC“第一场”网络赛 GCD on Sequence(思维,线段树)

GCD on Sequence

原题链接

在这里插入图片描述

我们可以发现,假设区间 [ l , r ] [l,r] [l,r] v ( l , r ) = d v(l,r)=d v(l,r)=d ,那么 [ l , r ] [l,r] [l,r] 内肯定至少有两个 d d d 的倍数。我们可以通过从 n n n 1 1 1 去枚举 g c d gcd gcd ,设其为 d d d ,然后找到所有 d d d 的倍数的下标。我们可以先不考虑其它 g c d gcd gcd 对此次的计算影响,只考虑计算 m a x _ g c d = d max\_gcd=d max_gcd=d 的最终答案。那么假设处理出来了其倍数下标排序后为 [ 1 , 3 , 5 , 10 ] [1,3,5,10] [1,3,5,10] ,那么我们怎么计算有多少个区间满足 v ( l , r ) = d v(l,r)=d v(l,r)=d 呢?我们考虑枚举左端点 l l l l ∈ [ 1 , 1 ] l∈[1,1] l[1,1] r r r 3 3 3 及以后皆可; l ∈ [ 2 , 3 ] l∈[2,3] l[2,3] r r r 5 5 5 及其以后, l ∈ [ 4 , 5 ] l∈[4,5] l[4,5] r r r 5 5 5 到尾。到这里应该找到规律了吧,也就是枚举每个下标 p [ i ] p[i] p[i] ,其合理左端点区间为 [ p [ i − 1 ] + 1 , p [ i ] ] [p[i-1]+1,p[i]] [p[i1]+1,p[i]] ,对应的右端点为 [ p [ i + 1 ] , n ] [p[i+1],n] [p[i+1],n] 。 那么现在考虑其它 g c d gcd gcd 的影响,其实也就是更大的 g c d gcd gcd 对计算的影响。我们就想我们是否可以枚举每个左端点进行计算的时候,是否可以把更大的答案的影响消去。那么其实我们对于每个左端点 l l l 维护尚未确定 v v v 值的最大的右端点 m x [ l ] mx[l] mx[l] ,然后我们可以发现一个性质, m x [ l ] mx[l] mx[l] 是单调递增的,因为假设一个左端点 l 1 l_1 l1 的值为 m x [ l 1 ] mx[l_1] mx[l1] ,假设 l 2 > l 1 l_2>l_1 l2>l1 m x [ l 2 ] < m x [ l 1 ] mx[l_2]<mx[l_1] mx[l2]<mx[l1] ,也就是说 [ l 2 , m x [ l 2 ] + 1 ] [l_2,mx[l_2]+1] [l2,mx[l2]+1] 这个区间是已经确定了 v v v 值的,而这个区间一定是 [ l 1 , m x [ l 1 ] ] [l_1,mx[l_1]] [l1,mx[l1]] 的一个子区间,子区间都确定了 v v v 值,那父区间也必确定了 v v v 值,相互矛盾,这个情况不可能发生。所以我们只要用线段树维护 m x [ i ] mx[i] mx[i] 的区间最大值和区间和,我们在枚举 [ p [ i − 1 ] + 1 , p [ i ] ] [p[i-1]+1,p[i]] [p[i1]+1,p[i]] 的时候,就可以通过在线段树上二分的方法找到一个需要更新的左端点的最小值 L L L ,然后就可以将 [ L , p [ i ] ] [L,p[i]] [L,p[i]] 区间覆盖为 p [ i + 1 ] − 1 p[i+1]-1 p[i+1]1 ,然后不断枚举左端点继续往后更新。 更 新 前 的 根 节 点 区 间 和 更新前的根节点区间和 所 有 更 新 都 完 成 后 的 根 节 点 的 区 间 和 所有更新都完成后的根节点的区间和 的差即是这个点的答案。

#include <bits/stdc++.h>
#define lson rt<<1
#define rson (rt<<1)|1

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n, a[N], rk[N];

ll sum[N << 2];
int lz[N << 2], mx[N << 2];

int pos[N], len;

ll ans[N];

void push_up(int rt) {
    sum[rt] = sum[lson] + sum[rson];
    mx[rt] = max(mx[lson], mx[rson]);
}

void push_down(int rt, int l, int r) {
    if (!lz[rt]) return ;
    int mid = l + r >> 1;
    lz[lson] = lz[rson] = lz[rt];
    mx[lson] = mx[rson] = lz[rt];
    sum[lson] = 1ll * (mid - l + 1) * lz[rt];
    sum[rson] = 1ll * (r - mid) * lz[rt];
    lz[rt] = 0;
}

void build(int rt, int l, int r) {
    lz[rt] = 0;
    if (l == r) {
        sum[rt] = n;
        mx[rt] = n;
        return ;
    }
    int mid = l + r >> 1;
    build(lson, l, mid);
    build(rson, mid + 1, r);
    push_up(rt);
}

void update(int rt, int l, int r, int L, int R, int v) {
    if (L <= l && r <= R) {
        sum[rt] = 1ll * (r - l + 1) * v;
        mx[rt] = v;
        lz[rt] = v;
        return ;
    }
    push_down(rt, l, r);
    int mid = l + r >> 1;
    if (mid >= L) update(lson, l, mid, L, R, v);
    if (mid < R) update(rson, mid + 1, r, L, R, v);
    push_up(rt);
}

int fd(int rt, int l, int r, int L, int R, int v) {
    if (l == r) {
        if (l > R || mx[rt] <= v) return -1;
        else if (l < L) return L;
        else return l;
    }
    push_down(rt, l, r);
    int mid = l + r >> 1;
    if (mx[lson] > v) return fd(lson, l, mid, L, R, v);
    else return fd(rson, mid + 1, r, L, R, v);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            rk[a[i]] = i;
        }
        build(1, 1, n);
        for (int i = n; i >= 1; --i) {
            len = 0;
            for (int j = i; j <= n; j += i) {
                pos[++len] = rk[j];
            }
            sort(pos + 1, pos + 1 + len);
            ans[i] = sum[1];
            for (int j = 1; j + 1 <= len; ++j) {
                int l = fd(1, 1, n, pos[j - 1] + 1, pos[j], pos[j + 1] - 1);
                if (~l) {
                    update(1, 1, n, l, pos[j], pos[j + 1] - 1);
                }
            }
            ans[i] -= sum[1];
        }
        for (int i = 1; i <= n; ++i) {
            printf("%lld\n", ans[i]);
        }
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值