HDU 5497 Inversion(树状数组求逆序对)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5497
题意很明确:你有一个序列{a1,a2,…,an},然后你可以删除一个长度为m的连续子序列. 问如何删除才能使逆序对最少.(1≤n≤10​5​​,1≤m≤n)
这是官方给出的题解:令gi​​表示在i前面比ai​​大的数的个数, fi​​表示在i后面比ai​​小的数的个数, 这两个都可以用树状数组轻松求出来. 那么对于一个长度L的连续子序列, 删掉它之后逆序对减少的个数就是这段区间中gi​​的和 + 这段区间fi​​的和 - 这段区间的逆序对个数. 求区间逆序对个数只要用一个树状数组维护就好了, 每次只是删除最左端的一个数和加入最右端的一个数, 分别统计下贡献.
我也是看了下题解再做这道题目的。
其实认真读题解,这道题目就感觉非常简单了,我也不做过多的解释。做了这道题目,最大的收获就是:对于序列+区间问题,我们应该怎样的构造出一个区间减法,它可能带一点容斥原理的味道,也许还有统计贡献的影子。先思考出合法的区间减法(这就考思维了,反正我是菜鸟,还有待提高),再考虑算法和数据结构。
下面是我敲了一个多小时的代码,可能开始的时候考虑的不是那么清楚,所以敲的时间太长了。甚是丑陋,勿喷,还请多多指教。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1e5 + 1;
typedef long long ll;
int bit[MAXN], BIT[MAXN], arr[MAXN], last[MAXN], Next[MAXN], N;
void updata(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;
}
void UPDATA(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;
}
ll min(ll a, ll b) {
    return a <= b ? a : b;
}
int main() {
    int T;
    scanf("%d", &T);
    for (int I = 1; I <= T; ++I) {
        int M;
        scanf("%d%d", &N, &M);
        for (int i = 1; i <= N; ++i) scanf("%d", arr + i);
        ll _sum = 0;
        memset(bit, 0, sizeof bit);
        for (int i = 1; i <= N; ++i) {
            last[i] = i - 1 - sum(arr[i]);//sum(arr[i])的结果是前面1到i-1中有多少个比arr[i]小的数,总共有i-1个数。
            _sum += last[i];
            updata(arr[i], 1);//先询问,再更新
        }//last[i]记录前面1到i-1中有多少个比arr[i]大的数。
        memset(bit, 0, sizeof bit);
        for (int i = N; i >= 1; --i) {
            Next[i] = sum(arr[i] - 1);
            updata(arr[i], 1);
        }//last[i]记录前面i+1到N中有多少个比arr[i]小的数。
        ll ans = 10000000000, templast = 0, tempnext = 0, temp = 0;
        memset(bit, 0, sizeof bit);
        memset(BIT, 0, sizeof BIT);
        //这里用两个树状数组维护,因为需要知道i-M+1到i这个区间内有多少个数比arr[i-M]小,还需要知道这个区间内有多少个数比arr[i+1]这个数大。
        for (int i = 1; i <= N; ++i) {
            if (i <= M) {
                updata(arr[i], 1);
                UPDATA(arr[i], 1);
                temp += last[i];
                templast += last[i];
                tempnext += Next[M - i + 1];
                if (i == M) ans = min(ans, _sum - templast - tempnext + temp);
            }
            else {
                updata(arr[i - M], -1);//必须先把arr[i - M]这个数从树状数组中去掉,不然询问有多少个数大于arr[i]时可能多加一个。
                int a = M - 1 - sum(arr[i]), b = SUM(arr[i - M] - 1);
                temp += a - b;
                updata(arr[i], 1);
                UPDATA(arr[i], 1);
                UPDATA(arr[i - M], -1);
                templast += last[i] - last[i - M];
                tempnext += Next[i] - Next[i - M];
            }
            if (i > M) ans = min(ans, _sum - templast - tempnext + temp);
        }
        printf("%lld\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值