NC 15557 线段树 + 尺取法

题意

传送门 NC 15557

题解

如果只考虑区间的 g c d gcd gcd,根据其性质
g c d ( a , b , c , …   ) = g c d ( a , g c d ( b , c , …   ) ) gcd(a,b,c,\dots)=gcd(a,gcd(b,c,\dots)) gcd(a,b,c,)=gcd(a,gcd(b,c,)) 线段树维护区间 g c d gcd gcd 即可;合并满足子区间 g c d gcd gcd 等于区间 g c d gcd gcd 的子区间数需要维护更多信息。

考虑区间拓展其 g c d gcd gcd 的不减性质
g c d ( a , b ) ≥ g c d ( a , b , c ) gcd(a,b)\geq gcd(a,b,c) gcd(a,b)gcd(a,b,c) 那么可以用尺取法统计满足条件的子区间数;若 g c d gcd gcd 减小,相对于原值至少约去一个质因子,考虑数据范围 [ 0 , 1 0 9 ] [0,10^9] [0,109],那么对于区间前缀或者后缀的 g c d gcd gcd 最多有 32 32 32 (最小质因子 2 2 2 以及数据范围最小值 0 0 0,考虑 2 30 2^{30} 230)个不同的值且是区间连续的。那么可以将区间左右端点拓展(即 [ l , i ] , [ i , r ] , i ∈ [ l , r ] [l,i],[i,r],i\in [l,r] [l,i],[i,r],i[l,r])的各子区间压缩,维护 g c d gcd gcd 相等的连续区间及其长度。此时可以在 O ( l o g ( m a x { a i } ) ) O(log(max\{a_i\})) O(log(max{ai})) 实现区间合并,总复杂度 O ( n × l o g ( m a x { a i } ) + q × l o g ( n ) × l o g ( m a x { a i } ) ) O(n\times log(max\{a_i\})+q\times log(n)\times log(max\{a_i\})) O(n×log(max{ai})+q×log(n)×log(max{ai}))

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 100005
#define t_size (1 << 18) - 1
typedef long long ll;
struct seg{int x, n;};
struct node {ll cnt; int gcd, nl, nr; seg l[25], r[25];} tree[t_size];
int T, N, Q, A[maxn], L[maxn], R[maxn];

node merge(node &cl, node &cr)
{
    node p;
    p.gcd = __gcd(cl.gcd, cr.gcd);
    p.cnt = (cl.gcd == p.gcd ? cl.cnt : 0) + (cr.gcd == p.gcd ? cr.cnt : 0);
    for (int i = 0, j = cr.nl - 1, rs = 0; i < cl.nr; i++)
    {
        while (j >= 0 && __gcd(cl.r[i].x, cr.l[j].x) == p.gcd)
        {
            rs += cr.l[j].n;
            --j;
        }
        p.cnt += 1LL * cl.r[i].n * rs;
    }
    p.nl = cl.nl, p.nr = cr.nr;
    for (int i = 0; i < cl.nl; i++) p.l[i] = cl.l[i];
    for (int i = 0; i < cr.nr; i++) p.r[i] = cr.r[i];
    for (int i = 0; i < cr.nl; i++)
    {
        int t = __gcd(p.l[p.nl - 1].x, cr.l[i].x);
        if (p.l[p.nl - 1].x == t) p.l[p.nl - 1].n += cr.l[i].n;
        else p.l[p.nl++] = seg{t, cr.l[i].n};
    }
    for (int i = 0; i < cl.nr; i++)
    {
        int t = __gcd(p.r[p.nr - 1].x, cl.r[i].x);
        if (p.r[p.nr - 1].x == t) p.r[p.nr - 1].n += cl.r[i].n;
        else p.r[p.nr++] = seg{t, cl.r[i].n};
    }
    return p;
}

void init(int k, int l, int r)
{
    if (r - l == 1)
    {
        node &p = tree[k];
        p.gcd = A[l];
        p.cnt = p.nl = p.nr = 1;
        p.l[0] = p.r[0] = seg{A[l], 1};
    }
    else
    {
        int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (r + l) >> 1;
        init(chl, l, m);
        init(chr, m, r);
        tree[k] = merge(tree[chl], tree[chr]);
    }
}

node query(int a, int b, int k, int l, int r)
{
    if (a <= l && r <= b) return tree[k];
    int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
    if (b <= m) return query(a, b, chl, l, m);
    if (m <= a) return query(a, b, chr, m, r);
    node p1 = query(a, b, chl, l, m), p2 = query(a, b, chr, m, r);
    return merge(p1, p2);
}

int main()
{
    scanf("%d", &T);
    for (int i = 1; i <= T; i++)
    {
        scanf("%d", &N);
        for (int i = 0; i < N; i++) scanf("%d", A + i);
        init(0, 0, N);
        scanf("%d", &Q);
        for (int i = 0; i < Q; i++) scanf("%d%d", L + i, R + i);
        printf("Case #%d:\n", i);
        for (int i = 0; i < Q; i++)
        {
            node p = query(L[i] - 1, R[i], 0, 0, N);
            printf("%d %lld\n", p.gcd, p.cnt);
        }
    }
    return 0;
}

测试没有卡 2 30 2^{30} 230 这样的数据,数组开小些也能过,开到 32 32 32 直接 T L E TLE TLE;显然用 v e c t o r vector vector 维护左右端点拓展区间的信息才是正解。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define maxn 100005
#define t_size (1 << 18) - 1
typedef long long ll;
struct seg{int x, n;};
struct node {ll cnt; int gcd; vector<seg> l, r;} tree[t_size];
int T, N, Q, A[maxn], L[maxn], R[maxn];

node merge(node &cl, node &cr)
{
    node p;
    p.gcd = __gcd(cl.gcd, cr.gcd);
    p.cnt = (cl.gcd == p.gcd ? cl.cnt : 0) + (cr.gcd == p.gcd ? cr.cnt : 0);
    for (int i = 0, j = cr.l.size() - 1, rs = 0; i < cl.r.size(); i++)
    {
        while (j >= 0 && __gcd(cl.r[i].x, cr.l[j].x) == p.gcd)
        {
            rs += cr.l[j].n;
            --j;
        }
        p.cnt += 1LL * cl.r[i].n * rs;
    }
    p.l = cl.l, p.r = cr.r;
    for (int i = 0; i < cr.l.size(); i++)
    {
        int t = __gcd(p.l[p.l.size() - 1].x, cr.l[i].x);
        if (p.l[p.l.size() - 1].x == t) p.l[p.l.size() - 1].n += cr.l[i].n;
        else p.l.push_back(seg{t, cr.l[i].n});
    }
    for (int i = 0; i < cl.r.size(); i++)
    {
        int t = __gcd(p.r[p.r.size() - 1].x, cl.r[i].x);
        if (p.r[p.r.size() - 1].x == t) p.r[p.r.size() - 1].n += cl.r[i].n;
        else p.r.push_back(seg{t, cl.r[i].n});
    }
    return p;
}

void init(int k, int l, int r)
{
    if (r - l == 1)
    {
        node &p = tree[k];
        p.gcd = A[l], p.cnt = 1;
        p.l.clear();
        p.r.clear();
        p.l.push_back(seg{A[l], 1});
        p.r.push_back(seg{A[l], 1});
    }
    else
    {
        int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (r + l) >> 1;
        init(chl, l, m);
        init(chr, m, r);
        tree[k] = merge(tree[chl], tree[chr]);
    }
}

node query(int a, int b, int k, int l, int r)
{
    if (a <= l && r <= b) return tree[k];
    int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
    if (b <= m) return query(a, b, chl, l, m);
    if (m <= a) return query(a, b, chr, m, r);
    node p1 = query(a, b, chl, l, m), p2 = query(a, b, chr, m, r);
    return merge(p1, p2);
}

int main()
{
    scanf("%d", &T);
    for (int i = 1; i <= T; i++)
    {
        scanf("%d", &N);
        for (int i = 0; i < N; i++) scanf("%d", A + i);
        init(0, 0, N);
        scanf("%d", &Q);
        for (int i = 0; i < Q; i++) scanf("%d%d", L + i, R + i);
        printf("Case #%d:\n", i);
        for (int i = 0; i < Q; i++)
        {
            node p = query(L[i] - 1, R[i], 0, 0, N);
            printf("%d %lld\n", p.gcd, p.cnt);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值