hdu 5381 The sum of gcd (线段树x树状数组x区间和维护进阶x离线处理)

题意:
给定区间,求所有子区间gcd之和。区间gcd定义为区间中所有数的gcd。
思路:
从一个点开始,朝某个方向一直求gcd,会形成一个非增序列,且下降不超过32次。(每次下降至少减半)
所以在线的思路就是用线段树,每个节点代表一个区间,sum保存这个区间所有子区间的gcd和,同时保存两个状态集合,分别是从左端点开始的连续区间,和从右端点开始的连续区间。参考了 Dora 的代码。

同时,离线的思路是:
sum[i] 存以i为左端点的所有区间的gcd和。然后从1到n扫一遍,通过前缀和相减就可以求得区间和。设当前扫到ai,需要更新它前面的所有sum。但是新增的区间都是以ai为右端点的,所以只需要不超过32次区间更新。

在线code

const int N = 10000 + 5;
typedef long long LL;

int gcd(int a, int b) {
    for (int t; b; t = a, a = b, b = t % b);
    return a;
}

struct part {
    int cnt, g;
};

struct node {
    vector<part> L, R;
    LL sum;

    void upd(const node& rhs) {
        sum += rhs.sum;
        for (int i = 0; i < R.size(); ++ i) {
            int ng = R[i].g;
            for (int j = 0; j < rhs.L.size(); ++ j) {
                ng = gcd(ng, rhs.L[j].g);
                sum += (LL)(ng)  * R[i].cnt * rhs.L[j].cnt;
            }
        }

        for (int i = 0; i < rhs.L.size(); ++ i) Insert(L, rhs.L[i]);
        vector<part> tmp(rhs.R);
        for (int i = 0; i < R.size(); ++ i) Insert(tmp, R[i]);
        R.swap(tmp);
    }

    static void Insert(vector<part>& vec, const part& x) {
        int ng = gcd(vec.back().g, x.g);
        if ( ng == vec.back().g ) {
            vec.back().cnt += x.cnt;
        } else {
            vec.push_back( (part) {x.cnt, ng} );
        }
    }
};

node tree[N*4];
#define lc o<<1
#define rc o<<1|1

void print(const node& x, int l, int r) {
    cout << "node "  << l << ' ' << r << endl;
    cout << "  sum: " << x.sum << endl;
    cout << "  L: ";for(auto i:x.L) cout << "(" << i.cnt << " , " << i.g << ") ";cout << endl;
    cout << "  R: ";for(auto i:x.R) cout << "(" << i.cnt << " , " << i.g << ") ";cout << endl;
    cout << endl;
}

void build(int o, int l, int r) {
    if ( l != r ) {
        int m = (l + r) >> 1;
        build(lc, l, m);
        build(rc, m+1, r);
        tree[o] = tree[lc];
        tree[o].upd(tree[rc]);
    } else {
        int x;
        scanf("%d", &x);
        tree[o].sum = x;
        vector<part>().swap(tree[o].L);
        vector<part>().swap(tree[o].R);
        tree[o].L.push_back( (part) { 1, x } );
        tree[o].R.push_back( (part) { 1, x } );
    }
    //print(tree[o], l, r);
}

int qL, qR;
node qres;
void query(int o, int l, int r) {
    if ( qL <= l && r <= qR ) {
        if ( l == qL )
            qres = tree[o];
        else
            qres.upd(tree[o]);
        return;
    }
    int m = (l + r) >> 1;
    if ( qL <= m ) query(lc, l, m);
    if ( qR > m ) query(rc, m+1, r);
}

int main() {
#ifdef _LOCA_ENV_
    freopen("input.in", "r", stdin);
#endif // _LOCA_ENV
    int t;
    scanf("%d", &t);
    while (t--) {
        int n, q;
        scanf("%d", &n);
        build(1, 1, n);
        scanf("%d", &q);
        rep(i, 1, q) {
            scanf("%d%d", &qL, &qR);
            //cout << "query " << qL << ' ' << qR << endl;
            query(1, 1, n);
            printf("%I64d\n", qres.sum);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值