ACM 线段树,树状数组入门题(附代码解释)

如果是初学者建议先看看这篇博客,写的很不错传送门

目录

HDU 1166 敌兵布阵(线段树)

HDU 1698 Just a Hook(线段树)

POJ 3468 A Simple Problem with Integers(线段树区间修改+求和)

HDU 1540 Tunnel Warfare(最长连续区间+单点修改)

洛谷 P3372 【模板】线段树 1

洛谷 P3373 【模板】线段树 2

洛谷 P1198 [JSOI2008]最大数(线段树或RMQ)

洛谷 P1531 I Hate It(线段树或树状数组)

2018年全国多校算法寒假训练营练习比赛(第五场)逆序数(树状数组)

2018年全国多校算法寒假训练营练习比赛(第五场)H Tree Recovery(线段树)

power oj 2821: 小Y学长的GCD难题(线段树区间求最大公因数+区间修改)

区间 (interval)(牛客小白月赛5)(差分)

POJ2299 Ultra-QuickSort(树状数组求逆序数)

HDU4000 Fruit Ninja(树状数组)

POJ2481 Cows(树状数组)

POJ 1990 MooFest(树状数组)

POJ 3264 Balanced Lineup(RMQ)


HDU 1166 敌兵布阵(线段树)

题解:线段树的模板题,但是用树状数组更简单一点,这里前面的这个树状数组的代码,后面的是线段树的代码。

树状数组:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 1000;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int bit[maxn];

int lowbit(int x) {
    return x & (-x);
}

void update(int x, int c) {
    while (x < maxn) {
        bit[x] += c;
        x += lowbit(x);
    }
}

int qeroy(int x) {
    int sum = 0;
    while (x) {
        sum += bit[x];
        x -= lowbit(x);
    }
    return sum;
}

int main() {
    int t;
    cin >> t;
    for (int tt = 1; tt <= t; tt++) {
        cout << "Case " << tt << ":" << endl;
        me(bit, 0);
        char s[10];
        int n, a, b, x;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            update(i, x);
        }
        while (scanf("%s", s) != EOF) {
            if (s[0] == 'E')
                break;
            scanf("%d%d", &a, &b);
            if (s[0] == 'Q')
                printf("%d\n", qeroy(b) - qeroy(a - 1));
            else if (s[0] == 'A')
                update(a, b);
            else
                update(a, -b);
        }
    }
    return 0;
}

线段树:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int sum[maxn << 2];

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &sum[rt]);
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

int num(int L, int R, int l, int r, int rt) {
    if (l >= L && R >= r)
        return sum[rt];
    int m = (l + r) >> 1;
    int s = 0;
    if (L <= m)
        s += num(L, R, l, m, rt << 1);
    if (R > m)
        s += num(L, R, m + 1, r, rt << 1 | 1);
    return s;
}

void update(int s, int c, int l, int r, int rt) {
    if (l == r) {
        sum[rt] += c;
        return;
    }
    int m = (l + r) >> 1;
    if (s <= m)
        update(s, c, l, m, rt << 1);
    else
        update(s, c, m + 1, r, rt << 1 | 1);
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

int main() {
    int t;
    cin >> t;
    for (int tt = 1; tt <= t; tt++) {
        cout << "Case " << tt << ":" << endl;
        me(sum, 0);
        int n;
        string s;
        scanf("%d", &n);
        build(1, n, 1);
        while (cin >> s) {
            if (s == "End")
                break;
            int a, b;
            scanf("%d%d", &a, &b);
            if (s == "Query")
                printf("%d\n", num(a, b, 1, n, 1));
            else if (s == "Add")
                update(a, b, 1, n, 1);
            else
                update(a, -b, 1, n, 1);
        }
    }
    return 0;
}

 

HDU 1698 Just a Hook(线段树)

题解:这个题看题就知道是一个很裸线段树的区间修改问题。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
typedef long long ll;
using namespace std;
int num[maxn << 2], add[maxn << 2];

void up_data(int rt) {
    num[rt] = num[rt << 1] + num[rt << 1 | 1];
}

void build(int l, int r, int rt) {
    if (l == r) {
        num[rt] = 1;
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    up_data(rt);
}

void push_down(int rt, int l, int r) {
    if (add[rt]) {
        int m = (l + r) >> 1;
        num[rt << 1] = (m - l + 1) * add[rt];
        num[rt << 1 | 1] = (r - m) * add[rt];
        add[rt << 1] = add[rt << 1 | 1] = add[rt];
        add[rt] = 0;
    }
}

void push_data(int x, int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r) {
        num[rt] = (r - l + 1) * x;
        add[rt] = x;
        return;
    }
    push_down(rt, l, r);
    int m = (l + r) >> 1;
    if (L <= m)
        push_data(x, L, R, l, m, rt << 1);
    if (R > m)
        push_data(x, L, R, m + 1, r, rt << 1 | 1);
    up_data(rt);
}

int main() {
    int t, Case = 1;
    cin >> t;
    while (t--) {
        me(num, 0), me(add, 0);
        int n, m;
        cin >> n;
        build(1, n, 1);
        cin >> m;
        while (m--) {
            int l, r, x;
            scanf("%d%d%d", &l, &r, &x);
            push_data(x, l, r, 1, n, 1);
        }
        cout << "Case " << Case++ << ": The total value of the hook is " << num[1] << "." << endl;
    }
    return 0;
}

 

POJ 3468 A Simple Problem with Integers(线段树区间修改+求和)

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
ll num[maxn << 2], add[maxn << 2];

void pushup(int rt) {
    num[rt] = num[rt << 1] + num[rt << 1 | 1];
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%lld", &num[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    pushup(rt);
}

void updata(int l, int r, int rt) {
    if (add[rt]) {
        int m = (l + r) >> 1;
        add[rt << 1] += add[rt];
        add[rt << 1 | 1] += add[rt];
        num[rt << 1] += add[rt] * (m - l + 1);
        num[rt << 1 | 1] += add[rt] * (r - m);
        add[rt] = 0;
    }
}

void pushdata(int L, int R, int c, int l, int r, int rt) {
    if (L <= l && R >= r) {
        num[rt] += (r - l + 1) * c, add[rt] += c;
        return;
    }
    int m = (l + r) >> 1;
    updata(l, r, rt);
    if (L <= m)
        pushdata(L, R, c, l, m, rt << 1);
    if (R > m)
        pushdata(L, R, c, m + 1, r, rt << 1 | 1);
    pushup(rt);
}

ll getsum(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return num[rt];
    ll sum = 0;
    updata(l, r, rt);
    int m = (l + r) >> 1;
    if (L <= m)
        sum += getsum(L, R, l, m, rt << 1);
    if (R > m)
        sum += getsum(L, R, m + 1, r, rt << 1 | 1);
    return sum;
}

int main() {
    int m, n;
    while (cin >> n >> m) {
        me(num, 0), me(add, 0);
        build(1, n, 1);
        while (m--) {
            char s[5];
            scanf("%s", s);
            if (s[0] == 'Q') {
                int x, y;
                scanf("%d%d", &x, &y);
                cout << getsum(x, y, 1, n, 1) << endl;
            } else {
                int x, y, c;
                scanf("%d%d%d", &x, &y, &c);
                pushdata(x, y, c, 1, n, 1);
            }
        }
    }
    return 0;
}

 

HDU 1540 Tunnel Warfare(最长连续区间+单点修改)
 

题意:

操作一:某个村庄被毁灭。

操作二:给出一个村庄的坐标,求包含包含这个村庄的的最长未被村庄的长度。

操作三:最后一个被摧毁的村庄被修复。

题解:首先我们可以想到用线段树来维护最长连续区间长度,我们现在可以用1代表改点没有被破坏,用0表示改点被破坏,然后用一个栈或数组来装上次破坏的点。然后就是线段树维护1的最长长度了。
 

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls[maxn << 2], rs[maxn << 2], sum[maxn << 2];
///sum表示区间最长连续序列,ls表示从左端点开始最长连续前缀,rs表示从r开始最长后缀
int n, m;

void push_up(int l, int r, int rt) {
    int mid = (l + r) >> 1;
    ls[rt] = ls[rt << 1];
    if (ls[rt << 1] == mid - l + 1)///要是左区间最长长度等于左区间长度,那么区间最长前缀长度要加上右儿子区间的最长前缀
        ls[rt] += ls[rt << 1 | 1];
    rs[rt] = rs[rt << 1 | 1];
    if (rs[rt << 1 | 1] == r - mid)///与上面情况一样
        rs[rt] += rs[rt << 1];
    sum[rt] = max(ls[rt], rs[rt]);
    sum[rt] = max(sum[rt], max(sum[rt << 1], sum[rt << 1 | 1]));
    sum[rt] = max(sum[rt], rs[rt << 1] + ls[rt << 1 | 1]);
    ///区间的最长连续长度为,左区间,右区间最长连续长度,和左区间的最长后缀+右区间的最长前缀的三者最大。(最后一种情况表示最长连续区间在中间)
}

void build(int l, int r, int rt) {
    if (l == r) {
        ls[rt] = rs[rt] = sum[rt] = 1;///初始化所有点都没有被破坏
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    push_up(l, r, rt);
}

void push_date(int pos, int x, int l, int r, int rt) {
    sum[rt] = ls[rt] = rs[rt] = 0;
    if (l == r) {
        sum[rt] = ls[rt] = rs[rt] = x;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        push_date(pos, x, lson);
    else
        push_date(pos, x, rson);
    push_up(l, r, rt);
}

int query(int pos, int l, int r, int rt) {
    if (l == r)
        return sum[rt];
    int mid = (l + r) >> 1;
    if (pos <= mid) {///点在左儿子区间里
        if (pos >= mid - rs[rt << 1] + 1)
            return ls[rt << 1 | 1] + rs[rt << 1];///要是点正好在左儿子的最长后缀中,肯定最长区间和就是左儿子的最长后缀长度+右儿子的最长前缀(左儿子的最长后缀与右儿子的最长前缀是连着的)
        else
            return query(pos, lson);///要是没有落在最长后缀中,就不用考虑是否加上右儿子的最长前缀,直接往下找就行了
    } else {
        if (pos <= mid + ls[rt << 1 | 1])///与上面情况类似
            return ls[rt << 1 | 1] + rs[rt << 1];
        else
            return query(pos, rson);
    }
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        build(1, n, 1);
        stack q;
        while (m--) {
            char str[2];
            int x;
            scanf("%s", str);
            if (str[0] == 'D') {
                scanf("%d", &x);
                push_date(x, 0, 1, n, 1);
                q.push(x);
            } else if (str[0] == 'Q') {
                scanf("%d", &x);
                printf("%d\n", query(x, 1, n, 1));
            } else {
                if (q.size()) {
                    push_date(q.top(), 1, 1, n, 1);
                    q.pop();
                }
            }
        }
    }
    return 0;
}

 

洛谷 P3372 【模板】线段树 1

题解:裸的线段树。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
ll sum[maxn << 2], add[maxn << 2];

void updata(int rt) {
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

void lazy(int l, int r, int rt) {
    if (add[rt]) {
        int m = (l + r) >> 1;
        sum[rt << 1] += (m - l + 1) * add[rt];
        sum[rt << 1 | 1] += (r - m) * add[rt];
        add[rt << 1] += add[rt];
        add[rt << 1 | 1] += add[rt];
        add[rt] = 0;
    }
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%lld", &sum[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    updata(rt);
}

void pushdata(int L, int R, int x, int l, int r, int rt) {
    if (L <= l && R >= r) {
        sum[rt] += (r - l + 1) * x;
        add[rt] += x;
        return;
    }
    lazy(l, r, rt);
    int m = (l + r) >> 1;
    if (L <= m)
        pushdata(L, R, x, l, m, rt << 1);
    if (R > m)
        pushdata(L, R, x, m + 1, r, rt << 1 | 1);
    updata(rt);
}

ll getsum(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return sum[rt];
    lazy(l, r, rt);
    int m = (l + r) >> 1;
    ll s = 0;
    if (L <= m)
        s += getsum(L, R, l, m, rt << 1);
    if (R > m)
        s += getsum(L, R, m + 1, r, rt << 1 | 1);
    return s;
}

int main() {
    int n, m;
    cin >> n >> m;
    build(1, n, 1);
    while (m--) {
        int a, x, y, k;
        scanf("%d", &a);
        if (a == 1) {
            scanf("%d%d%d", &x, &y, &k);
            pushdata(x, y, k, 1, n, 1);
        } else {
            scanf("%d%d", &x, &y);
            printf("%lld\n", getsum(x, y, 1, n, 1));
        }
    }
    return 0;
}

 

洛谷 P3373 【模板】线段树 2

题解:还是一个线段树的裸题,但是在区间修改的时候,他要乘上一个数,这时候懒标记下放,是要把下面要加的懒标记和要乘的懒标记的都要下放。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
typedef long long ll;
using namespace std;
ll num[maxn << 2], add[maxn << 2], ch[maxn << 2];
ll n, m, p;

void init(int n) {
    for (int i = 1; i <= 4 * n; i++)
        num[i] = add[i] = 0, ch[i] = 1;
}

void updata(int rt) {
    num[rt] = (num[rt << 1] + num[rt << 1 | 1]) % p;
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%lld", &num[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    updata(rt);
}

void push_down(int l, int r, int rt) {
    int m = (l + r) >> 1;
    add[rt << 1] = (add[rt << 1] * ch[rt] + add[rt]) % p;
    add[rt << 1 | 1] = (add[rt << 1 | 1] * ch[rt] + add[rt]) % p;
    ch[rt << 1] = (ch[rt << 1] * ch[rt]) % p;
    ch[rt << 1 | 1] = (ch[rt << 1 | 1] * ch[rt]) % p;
    num[rt << 1] = (num[rt << 1] * ch[rt] + (m - l + 1) * add[rt]) % p;
    num[rt << 1 | 1] = (num[rt << 1 | 1] * ch[rt] + (r - m) * add[rt]) % p;
    ch[rt] = 1, add[rt] = 0;
}

void push_data(int flog, int x, int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r) {
        if (flog == 1) {
            add[rt] = (add[rt] * x) % p;
            ch[rt] = (ch[rt] * x) % p;
            num[rt] = (num[rt] * x) % p;
        } else {
            add[rt] = (add[rt] + x) % p;
            num[rt] = (num[rt] + (r - l + 1) * x) % p;
        }
        return;
    }
    int m = (l + r) >> 1;
    push_down(l, r, rt);
    if (L <= m)
        push_data(flog, x, L, R, l, m, rt << 1);
    if (R > m)
        push_data(flog, x, L, R, m + 1, r, rt << 1 | 1);
    updata(rt);
}

ll get_sum(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return num[rt];
    int m = (l + r) >> 1;
    push_down(l, r, rt);
    ll s = 0;
    if (L <= m)
        s = (s + get_sum(L, R, l, m, rt << 1)) % p;
    if (R > m)
        s = (s + get_sum(L, R, m + 1, r, rt << 1 | 1)) % p;
    return s % p;
}

int main() {
    cin >> n >> m >> p;
    init(n);
    build(1, n, 1);
    while (m--) {
        int flog, x, y, k;
        scanf("%d", &flog);
        if (flog == 3) {
            scanf("%d%d", &x, &y);
            printf("%lld\n", get_sum(x, y, 1, n, 1));
        } else {
            scanf("%d%d%d", &x, &y, &k);
            push_data(flog, k, x, y, 1, n, 1);
        }
    }
    return 0;
}

 

洛谷 P1198 [JSOI2008]最大数(线段树或RMQ)

题解:这是一个裸的线段树和RMQ的题,这里把这两种代码都贴出来。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int ma[maxn][21], a[maxn];

void RMQ(int u) {
    ma[u][0] = a[u];
    for (int i = 1; (1 << i) <= u; i++)
        ma[u][i] = max(ma[u][i - 1], ma[u - (1 << (i - 1))][i - 1]);
}

int getmax(int l, int r) {
    int x = log(r - l + 1.0) / log(2.0);
    return max(ma[l][x], ma[l + (1 << x) - 1][x]);
}

int main() {
    int n, d;
    cin >> n >> d;
    int l = 0, t = 0;
    while (n--) {
        char str[5];
        scanf("%s", str);
        if (str[0] == 'A') {
            int x;
            scanf("%d", &x);
            a[++l] = (x + t) % d;
            RMQ(l);
        } else {
            int x;
            scanf("%d", &x);
            int ans = getmax(l - x + 1, l);
            printf("%d\n", ans);
            t = ans;
        }
    }
    return 0;
}

线段树:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 2e5 + 10;
const int mod = 1e9;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int ma[maxn << 2];

void updata(int rt) {
    ma[rt] = max(ma[rt << 1], ma[rt << 1 | 1]);
}

void build(int l, int r, int rt) {
    if (l == r) {
        ma[rt] = -inf;
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
}

void charu(int c, int x, int l, int r, int rt) {
    if (l == r) {
        ma[rt] = x;
        return;
    }
    int m = (l + r) >> 1;
    if (c <= m)
        charu(c, x, l, m, rt << 1);
    else
        charu(c, x, m + 1, r, rt << 1 | 1);
    updata(rt);
}

int getmax(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return ma[rt];
    int m = (l + r) >> 1;
    int temp = -2147283647;
    if (L <= m)
        temp = max(temp, getmax(L, R, l, m, rt << 1));
    if (R > m)
        temp = max(temp, getmax(L, R, m + 1, r, rt << 1 | 1));
    return temp;
}

int main() {
    int n, d, temp = 0, l = 0;
    cin >> n >> d;
    build(1, maxn, 1);
    while (n--) {
        char str[5];
        scanf("%s", str);
        if (str[0] == 'A') {
            int x;
            scanf("%d", &x);
            l++;
            charu(l, (x + temp) % d, 1, maxn, 1);
        } else {
            int x;
            scanf("%d", &x);
            temp = getmax(l - x + 1, l, 1, maxn, 1);
            printf("%d\n", temp);
        }
    }
    return 0;
}

 

洛谷 P1531 I Hate It(线段树或树状数组)

题解:裸的线段树。

线段树:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int ma[maxn << 2];

void updata(int rt) {
    ma[rt] = max(ma[rt << 1], ma[rt << 1 | 1]);
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &ma[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    updata(rt);
}

void pushdata(int c, int x, int l, int r, int rt) {
    if (l == r) {
        if (ma[rt] < x)
            ma[rt] = x;
        return;
    }
    int m = (l + r) >> 1;
    if (c <= m)
        pushdata(c, x, l, m, rt << 1);
    else
        pushdata(c, x, m + 1, r, rt << 1 | 1);
    updata(rt);
}

int getmax(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return ma[rt];
    int m = (l + r) >> 1;
    int ma1 = 0, ma2 = 0;
    if (L <= m)
        ma1 = getmax(L, R, l, m, rt << 1);
    if (R > m)
        ma2 = getmax(L, R, m + 1, r, rt << 1 | 1);
    return max(ma1, ma2);
}

int main() {
    int n, m;
    cin >> n >> m;
    me(ma, 0);
    build(1, n, 1);
    while (m--) {
        char str[2];
        int a, b;
        scanf("%s%d%d", str, &a, &b);
        if (str[0] == 'Q')
            printf("%d\n", getmax(a, b, 1, n, 1));
        else
            pushdata(a, b, 1, n, 1);
    }
    return 0;
}

 

2018年全国多校算法寒假训练营练习比赛(第五场)逆序数(树状数组)

题解:裸的数组树状数组求逆序数,所以可以声明一个数组,记录比当前位置大的数,因为逆序数,正好是前面出现的比后面大的数,所以只要在前面输入一个数,就把比他小的数的数组加一,最后直接加数组里的数,就是前面比当前输入数大的数的个数。

#include 
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int bit[maxn], n;

struct node {
    int x, i;

    bool friend operator<(node a, node b) {
        if (a.x == b.x)
            return a.i < b.i;
        return a.x < b.x;
    }
} a[maxn];

int lowbit(int x) {
    return x & (-x);
}

void updata(int x) {
    while (x <= n) {
        bit[x] += 1;
        x += lowbit(x);
    }
}

int ss(int x) {
    int s = 0;
    while (x) {
        s += bit[x];
        x -= lowbit(x);
    }
    return s;
}

int main() {
    while (~scanf("%d", &n) && n) {
        me(bit, 0);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i].x);
            a[i].i = i;
        }
        sort(a + 1, a + 1 + n);
        ll sum = 0;
        for (int i = 1; i <= n; i++) {
            updata(a[i].i);
            sum += i - ss(a[i].i);
        }
        cout << sum << endl;
    }
    return 0;
}

 

2018年全国多校算法寒假训练营练习比赛(第五场)H Tree Recovery(线段树)

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int num[maxn << 2], add[maxn << 2];

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &num[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    num[rt] = num[rt << 1] + num[rt << 1 | 1];
}

void pushdown(int ln, int rn, int rt) {
    if (add[rt]) {
        add[rt << 1] += add[rt];
        add[rt << 1 | 1] += add[rt];
        num[rt << 1] += add[rt] * ln;
        num[rt << 1 | 1] += add[rt] * rn;
        add[rt] = 0;
    }
}

void updata(int L, int R, int c, int l, int r, int rt) {
    if (L <= l && R >= r) {
        num[rt] += (r - l + 1) * c;
        add[rt] += c;
        return;
    }
    int m = (l + r) >> 1;
    pushdown(m - l + 1, r - m, rt);
    if (L <= m)
        updata(L, R, c, l, m, rt << 1);
    if (R > m)
        updata(L, R, c, m + 1, r, rt << 1 | 1);
    num[rt] = num[rt << 1] + num[rt << 1 | 1];
}

int query(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return num[rt];
    int s = 0;
    int m = (l + r) >> 1;
    pushdown(m - l + 1, r - m, rt);
    if (L <= m)
        s += query(L, R, l, m, rt << 1);
    if (R > m)
        s += query(L, R, m + 1, r, rt << 1 | 1);
    return s;
}

int main() {
    int n, m;
    cin >> n >> m;
    build(1, n, 1);
    while (m--) {
        string s;
        int a, b, c;
        cin >> s;
        if (s == "Q") {
            scanf("%d%d", &a, &b);
            cout << query(a, b, 1, n, 1) << endl;
        } else {
            scanf("%d%d%d", &a, &b, &c);
            updata(a, b, c, 1, n, 1);
        }
    }
    return 0;
}

 

power oj 2821: 小Y学长的GCD难题(线段树区间求最大公因数+区间修改)

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int d[maxn << 2], lazy[maxn << 2];

int gcd(int a, int b) {
    if (!b)
        return a;
    return gcd(b, a % b);
}

void update(int rt) {
    d[rt] = gcd(d[rt << 1], d[rt << 1 | 1]);
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &d[rt]);
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    update(rt);
}

void pushdown(int rt) {
    if (lazy[rt]) {
        d[rt << 1] = d[rt << 1 | 1] = lazy[rt];
        lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt];
        lazy[rt] = 0;
    }
}

void pushdate(int L, int R, int c, int l, int r, int rt) {
    if (L <= l && R >= r) {
        d[rt] = lazy[rt] = c;
        return;
    }
    pushdown(rt);
    int m = (l + r) >> 1;
    if (L <= m)
        pushdate(L, R, c, l, m, rt << 1);
    if (R > m)
        pushdate(L, R, c, m + 1, r, rt << 1 | 1);
    update(rt);
}

int getgcd(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r)
        return d[rt];
    pushdown(rt);
    int m = (l + r) >> 1;
    int a = 0, b = 0;
    if (L <= m)
        a = getgcd(L, R, l, m, rt << 1);
    if (R > m)
        b = getgcd(L, R, m + 1, r, rt << 1 | 1);
    return gcd(a, b);
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, q;
        cin >> n >> q;
        me(d, 0), me(lazy, 0);
        build(1, n, 1);
        while (q--) {
            int x, a, b, c;
            scanf("%d", &x);
            if (x == 1) {
                scanf("%d%d%d", &a, &b, &c);
                pushdate(min(a, b), max(a, b), c, 1, n, 1);
            } else {
                scanf("%d%d", &a, &b);
                printf("%d\n", getgcd(min(a, b), max(a, b), 1, n, 1));
            }
        }
    }
    return 0;
}

 

区间 (interval)(牛客小白月赛5)(差分)

题解:可以用线段树做,但是那样有点麻烦,这里可以用差分,这样只用更新,边界节点了。具体思想可以看代码理解。

#include 
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a) memset(a,0,sizeof(a))
typedef long long ll;
using namespace std;
ll num[maxn], a[maxn];

int main() {
    int n, m;
    while (cin >> n >> m) {
        me(num);
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &a[i]);
            num[i] = a[i] - a[i - 1];
        }
        while (m--) {
            int q, l, r, p;
            scanf("%d%d%d%d", &q, &l, &r, &p);
            if (q == 1) {
                num[l] -= p;
                num[r + 1] += p;
            } else {
                num[l] += p;
                num[r + 1] -= p;
            }
        }
        for (int i = 1; i <= n; i++)
            a[i] = a[i - 1] + num[i];
        int x, y;
        cin >> x >> y;
        ll sum = 0;
        for (int i = x; i <= y; i++)
            sum += a[i];
        cout << sum << endl;
    }
    return 0;
}

 

POJ2299 Ultra-QuickSort(树状数组求逆序数)

题解:前面已经有类似题了,但是没有说清楚,这里说的比较详细点。

这个需要用到离散化,

建立一个结构体包含val和id, val就是输入的数,id表示输入的顺序。然后按照val从小到大排序,如果val相等,那么就按照id排序。如果没有逆序的话,肯定id是跟i(表示拍好后的顺序)一直一样的,如果有逆序数,那么有的i和id是不一样的。所以,利用树状数组的特性,我们可以简单的算出逆序数的个数。

如果还是不明白的话举个例子。(输入4个数)

输入:9 -1 18 5

输出 3.

输入之后对应的结构体就会变成这样

val:9 -1 18 5

id:  1  2  3  4

排好序之后就变成了

val :  -1 5 9 18

id:      2 4  1  3

2 4 1 3 的逆序数 也是3

之后再利用树状数组的特性就可以解决问题了;

因为数字可能有重复, 所以添加操作不再单纯的置为1 ,而是 ++;

#include 
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int bit[maxn], n;

struct node {
    int x, i;

    bool friend operator<(node a, node b) {
        if (a.x == b.x)
            return a.i < b.i;
        return a.x < b.x;
    }
} a[maxn];

int lowbit(int x) {
    return x & (-x);
}

void updata(int x) {
    while (x <= n) {
        bit[x] += 1;
        x += lowbit(x);
    }
}

int ss(int x) {
    int s = 0;
    while (x) {
        s += bit[x];
        x -= lowbit(x);
    }
    return s;
}

int main() {
    while (~scanf("%d", &n) && n) {
        me(bit, 0);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i].x);
            a[i].i = i;
        }
        sort(a + 1, a + 1 + n);
        ll sum = 0;
        for (int i = 1; i <= n; i++) {
            updata(a[i].i);
            sum += i - ss(a[i].i);
        }
        cout << sum << endl;
    }
    return 0;
}

 

HDU4000 Fruit Ninja(树状数组)

题意:给出n个数,求所有的i

题解:题目也就是让最后那个数第二大。我们输入第i个数字a,那么我们用的树状数组存的就是sum(a-1),sum(a-1)表示在a这个位置,在前i-1个数中,有sum(a-1)个数比a小,那么我们就可以求出在后面的(i+1,n)的序列中,有R=(n-a)-(i-1-sum(a-1))个数比a要大,(n-a)表示有这么多个数比a大,(i-1-sum(a-1))表示前i-1个数里面有sum(a-1)个数比a大,那么R=(n-a)-(i-1-sum(a-1))就表示后面有多少个数比a要大。然后,我们思考一下,a当作三个数里面的最小值,然后让它们组合,是不是就是有C(R,2)即R*(R-1)/2,因为后面的序列都是固定的,所以并没有排序的概念,也就是说,原本序列1 5 4 ,那么我们没有必要去思考1 4 5的情况,因为它已经固定好了,所以后面的序列选的时候直接从R个选2个就可以了,然后C(R,2)表示所有的a+两个大的(也就是小中大,小大中都包含在里面了),那么我们要求的是小大中,所以对于每一个a来说,我们都需要减去小中大的情况,那么小中大=sum(a-1)*R,所以最后答案就是对于(1,n)所有数,求和SUM(C(R,2)-sum(a-1)*R。

#include 
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e5 + 5;
const int mod = 1e8 + 7;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int n, bit[maxn];

int lowbit(int x) {
    return x & (-x);
}

void updata(int x) {
    while (x <= n) {
        bit[x]++;
        x += lowbit(x);
    }
}

int getsum(int x) {
    int s = 0;
    while (x) {
        s += bit[x];
        x -= lowbit(x);
    }
    return s;
}

int main() {
    int t, Case = 0;
    cin >> t;
    while (t--) {
        me(bit, 0);
        scanf("%d", &n);
        ll sum = 0, x, l, r;///l,前面输入比x小的数的个数,r表示输入数中比x大的数的个数。
        for (int i = 0; i < n; i++) {
            scanf("%lld", &x);
            updata(x);
            l = getsum(x - 1);
            r = (n - x) - (i - l - 1);///(n-x)表示有这么多个数比x大,(i-1-l)表示前i-1个数里面有sum(x-1)个数比a大
            sum += r * (r - 1) / 2 - r * l;
        }
        printf("Case #%d: %lld\n", ++Case, sum % mod);
    }
    return 0;
}

 

POJ2481 Cows(树状数组)

题意:先说下题意吧,其实就是输入一些线段,问能找到这条线段最多包含几条线段If Si <= Sj and Ej <= Ei and Ei - Si > Ej - Sj。

题解:这样问题就很简单了,我们可以按照后节点从大到小排序,如果后节点相等,前节点从小到大排序。这样排完序后,我们只需要找前面比前节点小的节点的个数就行了。

#include 
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int n, bit[maxn];

struct node {
    int s, e, i;

    bool friend operator<(node a, node b) {
        if (a.e == b.e)
            return a.s < b.s;
        return a.e > b.e;
    }
} a[maxn];

int lowbit(int x) {
    return x & (-x);
}

void updata(int x) {
    while (x <= n) {
        bit[x]++;
        x += lowbit(x);
    }
}

int getsum(int x) {
    int sum = 0;
    while (x) {
        sum += bit[x];
        x -= lowbit(x);
    }
    return sum;
}

int main() {
    while (cin >> n && n) {
        int s[maxn];
        me(bit, 0);
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i].s, &a[i].e);
            a[i].i = i;
            a[i].s++;///因为s可能会为0,所以这里都加一
        }
        sort(a + 1, a + n + 1);
        for (int i = 1; i <= n; i++) {
            if (a[i].s == a[i - 1].s && a[i].e == a[i - 1].e)///若两区间相等,该值等于上一个的值
                s[a[i].i] = s[a[i - 1].i];
            else
                s[a[i].i] = getsum(a[i].s);///更新节点信息
            updata(a[i].s);
        }
        for (int i = 1; i <= n; i++) {
            if (i != n)
                printf("%d ", s[i]);
            else
                printf("%d\n", s[i]);
        }
    }
    return 0;
}

 

POJ 1990 MooFest(树状数组)

题意:当两头牛i,j交流的时候,交流的最小声音为max{v[i],v[j]}*他们之间的距离。现在有n头牛,求他们之间两两交流最少要的音量和。

题解:如果用暴力的话肯定会超时的,所以现在可以用树状数组。首先将牛按发出声音的值,按从小到大排序。然后声明两个数组,一个记录当前输入的牛的前面位置比它小的牛的个数,一个记录这些牛的位置和。现在就是怎么求当前牛与其他牛的不同值了。这个分两部分计算,一个是位置比当前牛小的牛和位置比当前牛大的牛。位置小的牛很好计算,就是当前牛的听力值乘以比当前牛位置小的所有牛的位置差。比它大的牛计算有点麻烦。现在用树状数组求出当前输入的所有牛的位置和,减去位置比它小的牛的位置和,位置比它大的牛的位置和。在乘以他的听力值,就是这头牛与其他牛的所有不同值得和,因为按听力排序的,所以当前牛的听力值是最大的,直接乘就行了。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 2e4 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
ll s[maxn], c[maxn];

struct node {
    int v, i;

    bool friend operator<(node a, node b) {
        if (a.v == b.v)
            return a.i < b.i;
        return a.v < b.v;
    }
} a[maxn];

int lowbit(int x) {
    return x & (-x);
}

void update(ll a[], int x, int s) {
    while (x <= maxn) {
        a[x] += s;
        x += lowbit(x);
    }
}

ll getsum(ll a[], int x) {
    int sum = 0;
    while (x) {
        sum += a[x];
        x -= lowbit(x);
    }
    return sum;
}

int main() {
    int n;
    while (~scanf("%d", &n)) {
        for (int i = 0; i < n; i++)
            scanf("%lld%lld", &a[i].v, &a[i].i);
        sort(a, a + n);
        ll sum = 0;
        for (int i = 0; i < n; i++) {
            ll xzs = getsum(c, a[i].i);//下标比他小的牛的个数
            ll xs = getsum(s, a[i].i);//下标比他小的牛的下标和
            ll ds = getsum(s, maxn);//当前输入所有牛的下标和
            sum += a[i].v * (xzs * a[i].i - xs + (ds - xs) - (i - xzs) * a[i].i);
            //(xzs*a[i].i-xs),位置比当前牛小的位置差的和。((ds-xs)-(i-xzs)*a[i].i)比当前牛位置大的位置差的和。
            update(c, a[i].i, 1);
            update(s, a[i].i, a[i].i);
        }
        printf("%lld\n", sum);
    }
    return 0;
}

 

POJ 3264 Balanced Lineup(RMQ)

题解:这个题可以用线段树维护,但是这里用RMQ更方便。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 10;
const int mod = 1e9 + 7;
const int inf = 1e8;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int ma[maxn][40], mi[maxn][40], n, m;

void inct() {
    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            ma[i][j] = max(ma[i][j - 1], ma[i + (1 << (j - 1))][j - 1]);
            mi[i][j] = min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
        }
}

void print(int l, int r) {
    int k = (int) (log(r - l + 1 + 0.0) / log(2.0));///k = log2(r - l + 1);
    int y = max(ma[l][k], ma[r - (1 << k) + 1][k]);
    int x = min(mi[l][k], mi[r - (1 << k) + 1][k]);
    cout << y - x << endl;
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        me(ma, 0), me(mi, 0);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &ma[i][0]);
            mi[i][0] = ma[i][0];
        }
        inct();
        while (m--) {
            int l, r;
            cin >> l >> r;
            print(l, r);
        }
    }
    return 0;
}

 

你可能感兴趣的:(数据结构,线段树,树状数组)