Luogu P3149 排序(树状数组、前缀和)

P3149

思路:

稍微模拟一下就能发现,每一次的操作其实就是将“第1~第k个位置上的值”的逆序对的贡献清零。

那么我们先对数据离散化后用树状数组求出每个位置上的值能贡献的逆序对,并使用前缀和加速逆序对的区间求值。

值得注意的一点是按照从大到小的顺序去修改树状数组就可以计算出每个位置上的值贡献的逆序对。

Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N = 300010;

struct Node {
	LL val, pos; //pos为第几个出现
	LL opt; //该点贡献的逆序对
	bool operator<(const Node& t) const {
		if (val == t.val) return pos < t.pos;
		else return val < t.val;
	}
} a[N];
LL tr[N], r[N], ta[N], atot;
LL n, m, ans, trans[N]/*第i个位置上的数是几*/;
LL sum[N];

void update(LL x, LL k) {
	for (; x <= n; x += x & -x)
		tr[x] += k;
}

LL query(LL x) {
	LL res = 0;
	for (; x; x -= x & -x) 
		res += tr[x];
	return res;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for (LL i = 1; i <= n; i++) {
		cin >> a[i].val;
		a[i].pos = i;
		ta[++atot] = a[i].val;
	}

	sort(ta + 1, ta + 1 + atot);
	atot = unique(ta + 1, ta + 1 + atot) - ta - 1; //atot为离散后的表长

	for (LL i = n; i >= 1; i--) { //倒着求就可以算出每个点所产生的逆序对的数量
		a[i].val = lower_bound(ta + 1, ta + 1 + atot, a[i].val) - ta; //离散化,即原来的数->原来的数是第几小的
		a[i].opt = query(a[i].val - 1); //在他之后还比他小的 即 a[i].val贡献的逆序对数量
		ans += a[i].opt;
		update(a[i].val, 1);
	}

	sort(a + 1, a + 1 + n); //求排序后的前缀和。因为要减掉的是小于等于第k个位置的值的逆序对总贡献
	for (LL i = 1; i <= n; i++) {
		sum[i] = sum[i - 1] + a[i].opt;
		trans[a[i].pos] = i; //[原来的数的位置]->在排序后的位置
	}

	cout << ans << endl;
	LL maxval = 0, nowopt = 0;
	for (LL i = 1; i <= m; i++) {
		LL k; cin >> k;
		if (a[trans[k]].val >= maxval) {
			nowopt = sum[n] - sum[trans[k]];
			cout << nowopt << endl;
			maxval = a[trans[k]].val;
		} else { //这一段已经排过序,不再贡献逆序对
			cout << nowopt << endl;
		}
	}
	return 0;
}

/*
4 2
1 4 2 3
4
3

5 2
2 6 3 5 4
3
4

*/
上一篇:情人节都用了哪些招术?


下一篇:ATA与AAT