HDU 5497 Inversion(树状数组求逆序对)
2015-10-05 16:57
357 查看
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5497
题意很明确:你有一个序列{a1,a2,…,an},然后你可以删除一个长度为m的连续子序列. 问如何删除才能使逆序对最少.(1≤n≤105,1≤m≤n)
这是官方给出的题解:令gi表示在i前面比ai大的数的个数, fi表示在i后面比ai小的数的个数, 这两个都可以用树状数组轻松求出来. 那么对于一个长度L的连续子序列, 删掉它之后逆序对减少的个数就是这段区间中gi的和 + 这段区间fi的和 - 这段区间的逆序对个数. 求区间逆序对个数只要用一个树状数组维护就好了, 每次只是删除最左端的一个数和加入最右端的一个数, 分别统计下贡献.
我也是看了下题解再做这道题目的。
其实认真读题解,这道题目就感觉非常简单了,我也不做过多的解释。做了这道题目,最大的收获就是:对于序列+区间问题,我们应该怎样的构造出一个区间减法,它可能带一点容斥原理的味道,也许还有统计贡献的影子。先思考出合法的区间减法(这就考思维了,反正我是菜鸟,还有待提高),再考虑算法和数据结构。
下面是我敲了一个多小时的代码,可能开始的时候考虑的不是那么清楚,所以敲的时间太长了。甚是丑陋,勿喷,还请多多指教。
题意很明确:你有一个序列{a1,a2,…,an},然后你可以删除一个长度为m的连续子序列. 问如何删除才能使逆序对最少.(1≤n≤105,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; }
相关文章推荐
- Codeforces Coder-Strike 2014 - Finals (online edition, Div. 1)
- HDU 1166 敌兵布阵
- POJ 2352 Stars
- PAT 1057 Stack (30)
- poj 2352
- 树状数组
- hdu 4630 No Pain No Game 树状数组
- 树状数组区间求和的三种模型
- HDOJ1166 敌兵布阵 树状数组
- 二维树状数组
- Hoj 2275 Number Sequence
- POJ3321 Apple Tree
- BZOJ2434 [Noi2011]阿狸的打字机【AC自动机+dfs序+树状数组】
- HDU 3015
- hdu 5147
- 【转载】区间信息的维护与查询(一)——二叉索引树(Fenwick树、树状数组)
- 树状数组模板
- HLG 1400 汽车比赛
- poj2299 树形数组&&离散化
- 树状数组小结