您的位置:首页 > 其它

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

2015-10-05 16:57 357 查看
题目链接: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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  树状数组