「树状数组」

题目传送门 poj1990
题意: 农夫的N (N∈[1, 20,000])头牛参加了"MooFest"之后有了不同程度的耳聋, 现在它们排在一条直线上,每头牛有2个属性值: 耳聋值v[i] (v[i] ∈[1, 20,000])和坐标x[i] (x[i]∈[1, 20,000]), 若两头牛之间要对话, 音量Voice = max(v[i], v[j]) * 相互的距离. 问所有N(N-1)/2对正在说话的牛产生的所有声音的总和.

朴素的算法超时,用的是树状数组解决,思路如下:
对于牛先基于耳聋值进行排序(下述前后皆指排序过后的相对位置), 这样做的好处是可以确定每头牛对话时, 到底考虑谁的音量: 由于是两两对话,我们可以对每头牛都只考虑位于自己前方的牛.

位于自己前方的牛中,不论是坐标比自己大还是比自己小的,音量都没自己大,所以都以自己的音量为准. 用两个树状数组来存储2个数据:
A: x坐标比自己小的牛的个数
B: x坐标比自己小的牛的个数的x值之和

那么在i牛之前,x轴坐标比自己小的距离绝对值即 A * x[i] - B

问: 那对于i牛,x坐标比自己大的怎么算呢? 首先,x坐标比自己大的牛 数量有 i - 1 - A, 其次对于计算到牛i而言, 每次都把当前牛的x轴坐标加到树状数组中, 所以前 20000项和(尚不包括第x[i]项)即所有在排序后数组中,位于牛i前面的牛x值之和, 减去B即所有x值比i大的牛x值之和. 再减去(i - 1 - A) * x[i], 即所有x值比i大的牛到第i牛的距离之和了. (这里因为巧妙用大数减小数去掉了绝对值)

树状数组: (用树状数组1)如何记录A? 每一头牛出现之后,把自己所在x位置+1, 那么前x[i]项和即A了, 因为x位置比x[i]大的不会被统计进来.

(用树状数组2)如何记录B? 每一头牛出现之后,把自己所在x位置+自己的x值, 那么统计前x[i]项和即B. 前20000项和即包括x值比自己大和比自己小的x值之和...

利用时间的先后和空间上的前后关系来统计..再十分啰嗦的举个莉子.
一个数组a[1...10]有10个元素,怎么统计7号元素(即a[7])之前有多少个比自己小的?, 设置一个vis数组={0},从第1到第6个元素每个都设置vis[a[i]] = 1, 统计前a[7]项(不包括第a[7]项的)和,即比自己小的元素个数了..同理统计A,B,只不过树状数组是快速实现了vis数组求和功能

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long ll;
typedef pair Cow;
const ll maxN = 20005, inf = 0x3f3f3f3f;
Cow cow[maxN];
ll bit[2][maxN];
ll N;

// 求前i项 和 第i项加x
ll sum(int i, int j) { ll ret = 0; while (i) { ret += bit[j][i]; i -= i & -i; } return ret; }
void add(int i, int x, int j) { while (i <= 20000) { bit[j][i] += x; i += i & -i; } }


// cow的first: volume, second: x coordinate
int main() {
    freopen("data.in", "r", stdin);
    scanf("%lld", &N);
    for (ll i = 1; i <= N; ++i)
        scanf("%lld %lld", &cow[i].first, &cow[i].second);
    sort(cow + 1, cow + 1 + N);

    ll ans = 0;
    // 对于每一头牛 考虑排序在自己之前的牛
    for (int i = 1; i <= N; ++i) {
        // a是x坐标比自己小的牛数量, b是x坐标比自己小的牛的x值之和
        ll a = sum(cow[i].second, 0), b = sum(cow[i].second, 1);
        ans += (cow[i].second * a - b + sum(20000, 1) - b - (i - 1 - a) * cow[i].second) *
                cow[i].first;
        add(cow[i].second, 1, 0);
        add(cow[i].second, cow[i].second, 1);
    }
    printf("%lld\n", ans);
    return 0;
}

hduoj 4417
给定N个数字的序列,后续有M个询问。询问给定l,r,H, 问[l,r]中,有多少个数字小于H。
这里使用树状数组解决,数组和询问都先记录下标,然后数组按数值从小到大排序,询问按H从小到大排序。之后从头遍历询问,每次只插入小于自己H的值的元素的下标,那么插完时,看[l,r]内有多少个值就可以了,为sum(r)-sum(l-1)

#include 
#include 
using namespace std;

#define pii pair
#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(ll i=(a);i<(b);++i)
#define fi first
#define se second
const double eps = 1e-8, PI = acos(-1.0f);
const int inf = 0x3f3f3f3f, maxN = 1e5 + 5;
int N, M, T;

int bit[maxN];
int add(int i, int x) {
    while (i <= N) { 
        bit[i] += x; 
        i += i & -i;
    }
}
int sum(int i) {
    int s = 0;
    while (i > 0) {
        s += bit[i];
        i -= i & -i;
    }
    return s;
}

pii nd[maxN];
struct Ques {
    int id, s, t, H;
} Q[maxN];
bool cmp(const Ques& a, const Ques& b) { return a.H < b.H; }

int ans[maxN];
int main() {
    // freopen("data.in", "r", stdin);
    scanf("%d", &T);
    rep(cas, 1, T + 1) {
        scanf("%d%d", &N, &M);
        rep(i, 1, N + 1) {
            scanf("%d", &nd[i].fi);
            nd[i].se = i;
        }

        int s, t, H;
        rep(i, 1, M + 1) {
            scanf("%d%d%d", &Q[i].s, &Q[i].t, &Q[i].H);
            ++Q[i].s; ++Q[i].t;
            Q[i].id = i;
        }

        sort(nd + 1, nd + 1 + N);
        sort(Q + 1, Q + 1 + M, cmp);
        mst(bit, 0);

        printf("Case %lld:\n", cas);
        // j: Ques, i: Node
        int i = 1, j = 1;
        while (j <= M) {
            while (i <= N) {
                if (nd[i].fi > Q[j].H)
                    break;
                add(nd[i].se, 1);
                ++i;
            }
            ans[Q[j].id] = sum(Q[j].t) - sum(Q[j].s - 1);
            ++j;
        }
        rep(i, 1, M + 1)
            printf("%d\n", ans[i]);
    }
    return 0;
}

你可能感兴趣的:(「树状数组」)