hdu5381 The sum of gcd

6 篇文章 0 订阅
6 篇文章 0 订阅

题面在这里

题意:

给一个序列,然后m次询问。
每次询问一个区间[l,r]中所有子区间的gcd之和。

做法:

这个题是nowcoder的比赛题,Wannafly挑战赛7的E
(然后被人找到了原题qaq。。
(一开始不会,从某个大佬口中一点点套出来的= =(果然大佬讲题都讲得飞快啊…

我们考虑把询问离线。
对于每一个右端点,维护它左端点的答案。
于是我们从左往右枚举i,表示当前区间右端点是i,线段树的节点x表示当x为左端点,时x~[x,i]的gcd和。

然后我们对于一个位子i,我们更新答案的时候要这么做:
从i开始向左连续一段区间的gcd如果都相同,比如[x,i]~[y,i]的gcd都是z,就在线段树[x,y]区间加上z。
然后,我们对发现gcd最多只有 logai 种,即我们数列中连续的段最多只有log段。
所以我们最多处理log次。

现在只剩下一个问题就是,怎样知道某一个gcd的对应区间是什么。
这个问题有很多种做法。
你可以保存一个倍增表,g[i][j]表示i开始向左跳2^j步,这段区间的gcd.
然后对于一个i,你要更新1~i这一段区间,于是你可以从i开始往左跳,跳到gcd不相同了就停止,更新线段树,然后再不停跳到1为止。

这个复杂度应该是 O(nlog2n) 的.

(其实我感觉这么说也不一定能一下子看懂。。可能需要自己仔细思考和研究下代码才行)

代码:

/*************************************************************
    Problem: hdu 5381 The sum of gcd
    User: fengyuan07
    Language: C++
    Result: Accepted
    Time: 296MS
    Memory: 15984K
    Submit_Time: 2018-01-06 19:01:40
*************************************************************/

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
#define mid (l+r>>1)
#define lc o<<1
#define rc o<<1|1
using namespace std;
typedef long long LL;

inline LL read()
{
    char ch = getchar(); LL x = 0; int op = 1;
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') op = -1;
    for (; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return op*x;
}
inline void write(LL a)
{
    if (a < 0) putchar('-'), a = -a;
    if (a >= 10) write(a/10); putchar('0'+a%10);
}

const int N = 100010;
int n, m, cnt;
int head[N], g[N][19];
LL sum[N<<2], tag[N<<2], ans[N];
struct Link{
    int l, id, nex;
    Link() {}
    Link(int x, int y, int z) { l = x, id = y, nex = z; }
}e[N];

inline int gcd(int a, int b) { return (!b)?a:gcd(b, a%b); }
inline void insert(int x, int a, int b)//x为右端点,a为左端点,b为询问的标号 
{ e[++ cnt] = Link(a, b, head[x]); head[x] = cnt; }
inline void pushdown(int o, int l, int r)
{
    if(!tag[o]) return;
    tag[lc] += tag[o]; tag[rc] += tag[o];
    sum[lc] += (mid-l+1)*tag[o]; sum[rc] += (r-mid)*tag[o];
    tag[o] = 0;
}
inline void update(int o, int l, int r, int x, int y, int z)
{
    if(l == x && r == y) { sum[o] += (LL)(r-l+1)*z; tag[o] += z; return; }
    pushdown(o, l, r);
    if(y <= mid) update(lc, l, mid, x, y, z);
    else if(x > mid) update(rc, mid+1, r, x, y, z);
    else update(lc, l, mid, x, mid, z), update(rc, mid+1, r, mid+1, y, z);
    sum[o] = sum[lc] + sum[rc];
}
inline LL query(int o, int l, int r, int x, int y)
{
    if(l == x && r == y) return sum[o];
    pushdown(o, l, r);
    if(y <= mid) return query(lc, l, mid, x, y);
    else if(x > mid) return query(rc, mid+1, r, x, y);
    else return query(lc, l, mid, x, mid) + query(rc, mid+1, r, mid+1, y);
}
int main()
{
    int Test = read();
    while(Test --) {
        cnt = 0; memset(head, 0, sizeof head); memset(g, 0, sizeof g);
        memset(sum, 0, sizeof sum); memset(tag, 0, sizeof tag);
        n = read();
        for(int i = 1; i <= n; i ++) g[i][0] = read();
        m = read();
        for(int i = 1; i <= m; i ++) { int l = read(), r = read(); insert(r, l, i); }
        for(int j = 1; j <= 17; j ++)
            for(int i = 1; i <= n; i ++) {
                if(i-(1<<j) < 0) continue;
                g[i][j] = gcd(g[i-(1<<j-1)][j-1], g[i][j-1]);
            }//g[i][j]表示i开始向左跳2^j步,这段区间的gcd
        for(int i = 1; i <= n; i ++) {
            int now = g[i][0], a = i-1, b = i;
            while(a >= 1) {
                for(int j = 17; j >= 0; j --)
                    if(g[a][j] && gcd(g[a][j], now) == now) a -= 1<<j;
                update(1, 1, n, a+1, b, now);
                now = gcd(now, g[a][0]); b = a; a --;
            }
            if(b) update(1, 1, n, a+1, b, now);
            for(int j = head[i]; j; j = e[j].nex) ans[e[j].id] = query(1, 1, n, e[j].l, i);
        }
        for(int i = 1; i <= m; i ++) write(ans[i]), puts("");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值