Array Optimization by Deque (cf)树状数组

原题链接:Problem - 1579E2 - Codeforces

今天学了树状数组,好牛,一个题想了一晚上才明白呜呜

1.首先这个题要想明白,每次插入一个数的时候,如果这个数插在队列的最前面,那么对ans有贡献的就是所有小于这个数的个数;如果插在队列的最后面,那么对ans有贡献的就是所有大于这个数的个数。因此也可以想到,每次插入一个数的时候,与前面的那些数的排列顺序无关,只跟它自己插在最前面还是最后面有关,所以找到前面和后面贡献的最小值就ok了。

2.数的范围较大,可以离散化将每个数变成它们相应的序号

这里有两种方法:

(1)排序&去重

去重了之后相同的数拥有相同的序号;

(2)排序&不去重

不去重的话相同的数有相同的序号,可能会出现序号为1 1 2 4 4这样的情况,即没有3这个序号

但是其实想一下,(1)也会最后少了一些后面的序号,所以没关系,最后用c[i]树状数组都是一样的用。

3.用树状数组维护区间

求小于等于一个序号的所有个数 : for (int j = a[i]; j; j -= j & -j) res1 += c[j];

是从这个序号往前,所以 j -= j & -j

这个地方还有一个点,求所有小于a[i]的个数,是用 for (int j = a[i]; j; j -= j & -j) res1 += c[j];

求所有大于a[i]的个数,是先算出所有小于等于a[i]的个数for (int j = a[i]; j; j -= j & -j) res2 += c[j];

然后,在这个数插入之前,队列中一共有i - 1个数,所以大于a[i]的个数为i - 1 - res2。

4.a[]最后储存的是原储存的a[]数组离散化后的各个值自己的序号,对a[]循环的时候,记得每次在最后for (int j = a[i]; j <= n; j += j & -j) c[j]++;

5.树状数组的下标(序号)是从1开始的,所以:

for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;

算序号的时候是减去b,相当于减去0;

6.因为有多个测试样例,记得每次归零:

  for (int i = 1; i <= n; i++) c[i] = 0;  (从1到n就行了,树状数组序号最大到n)

#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
#define rep(i, m, n) for (int i = (m); i <= (n); ++i)
#define rrep(i, m, n) for (int i = (m); i >= (n); --i)
typedef long long ll;

const int N = 200010;
int a[N], b[N], c[N];

int main() {
	int t, n;
	cin >> t;
	while (t--) {
		cin >> n;
		for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
		for (int i = 1; i <= n; i++) c[i] = 0;
		sort(b + 1, b + n + 1);
		for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;
		long long ans = 0;
		for (int i = 1; i <= n; i++) {
			int res1 = 0, res2 = 0;
			for (int j = a[i] - 1; j; j -= j & -j) res1 += c[j];
			for (int j = a[i]; j; j -= j & -j) res2 += c[j];
			res2 = i - 1 - res2;
			ans += min(res1, res2);
			for (int j = a[i]; j <= n; j += j & -j) c[j]++;
		}
		cout << ans << endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值