[CQOI2011]动态逆序对 【主席树+树状数组】

传送门

废话:这道题和当初队长他们去电子科技大学的校赛A题几乎是一样,这道题没有挂在他们的OJ上,无意之间发现了这道题,赶紧补一下。这道题的做法也太多了吧。。。。。分块会板子(这道题不会),CDQ分治不会,只会大佬说的动态主席树板子题,然后拿来改一下就能过了。。。

解题思路:求解逆序数,我们常常用到树状数字来解决。对于每一个数num[i]对逆序数的贡献:
(j<i)\&\&(num[j]>num[i])或者是(j>i)\&\&(num[j]<num[i]),对于删掉每一个数,我们只需要将他的贡献删除掉即可。
我们利用树状数组+主席树来维护状态即可。每次删除除掉一个数,就将他涉及到的每棵树都给更新一下,每次答案更新就是减去区间[ 1 ,i ]大于X值的个数和区间[ i ,n ]小于X值得个数。

注意事项:因为是树套树,注意内存的大小,用快读(我T在了最后一组数据,可能常数太大了,太菜了)

代码:

///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>

#define MT(a, b) memset(a,b,sizeof(a)
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai = acos(-1.0);
const double E = 2.718281828459;
const ll mod = 20071027;
const ll INF = 0x3f3f3f3f3f3f;
const int maxn = 1e5 + 5;

inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') ch == '-' && (f = -1), ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}

int n, op, pos[maxn];
struct node {
    int ls, rs, cnt;
#define left(a, b) p[a].ls, p[b].ls, l, mid
#define right(a, b) p[a].rs, p[b].rs, mid + 1, r
} p[maxn * 100];
int root[maxn], times;
int temp_l[maxn], temp_r[maxn], cntl, cntr;

void insert(int &now, int old, int l, int r, int pos, int x) {
    if (!now) now = ++times;
    p[now] = p[old], p[now].cnt += x;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) insert(left(now, old), pos, x);
    else insert(right(now, old), pos, x);
}

void update(int i, int pos, int x) {
    while (i <= n) {
        insert(root[i], root[i], 1, n, pos, x);
        i += lowbit(i);
    }
}

ll query(int l, int r, int x, int flag) {  ///flag=-1 ,查找下标小于pos[x]并且大于x  flag=1,反之
    int mid = (l + r) >> 1;
    ll sum = 0;
    if ((l >= x && flag == -1) || (r <= x && flag == 1)) {
        for (int i = 1; i <= cntr; i++) sum += p[temp_r[i]].cnt;
        for (int i = 1; i <= cntl; i++) sum -= p[temp_l[i]].cnt;
        return sum;
    }
    if (flag == -1) {
        for (int i = 1; i <= cntr; i++) {
            if (x <= mid) sum += p[p[temp_r[i]].rs].cnt;
            temp_r[i] = (x <= mid) ? p[temp_r[i]].ls : p[temp_r[i]].rs;
        }
        for (int i = 1; i <= cntl; i++) {
            if (x <= mid) sum -= p[p[temp_l[i]].rs].cnt;
            temp_l[i] = (x <= mid) ? p[temp_l[i]].ls : p[temp_l[i]].rs;
        }
        if (x > mid) return query(mid + 1, r, x, flag);
        else return sum + query(l, mid, x, flag);
    } else {
        for (int i = 1; i <= cntr; i++) {
            if (x > mid) sum += p[p[temp_r[i]].ls].cnt;
            temp_r[i] = (x > mid) ? p[temp_r[i]].rs : p[temp_r[i]].ls;
        }
        for (int i = 1; i <= cntl; i++) {
            if (x > mid) sum -= p[p[temp_l[i]].ls].cnt;
            temp_l[i] = (x > mid) ? p[temp_l[i]].rs : p[temp_l[i]].ls;
        }
        if (x <= mid) return query(l, mid, x, flag);
        else return sum + query(mid + 1, r, x, flag);
    }
}

void find_ans(int l, int r, int x, int flag, ll &ans, ll sym) {         //sym   ans更新的符号
    cntl = cntr = 0;
    for (int i = r; i; i -= lowbit(i)) temp_r[++cntr] = root[i];
    for (int i = l - 1; i; i -= lowbit(i)) temp_l[++cntl] = root[i];
    ans = ans + sym * query(1, n, x, flag);
}

int main() {
    int x;
    ll ans = 0;
    n = read(), op = read();
    for (int i = 1; i <= n; i++) {
        x = read();
        find_ans(1, i, x, -1, ans, 1LL);
        pos[x] = i;
        update(i, x, 1);
    }
    while (op--) {
        printf("%lld\n", ans);
        x = read();
        update(pos[x], x, -1);
        find_ans(1, pos[x] - 1, x, -1, ans, -1LL);
        find_ans(pos[x], n, x, 1, ans, -1LL);
    }
    return 0;
}

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值