【树状数组套动态开点线段树】求动态逆序对 洛谷P3157

题目描述

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

输入输出格式

输入格式:

 

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。

 

输出格式:

 

输出包含m行,依次为删除每个元素之前,逆序对的个数。

 

输入输出样例

输入样例#1: 复制

5 4
1
5
3
4
2
5
1
4
2

输出样例#1: 复制

5
2
2
1

样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。

说明

N<=100000 M<=50000

// 树状数组套动态开点线段树

#include <bits/stdc++.h>
#define ll long long
#define mid (l + r) / 2
using namespace std;
const int mn = 1e5 + 5;

int a[mn], p[mn];
int tr[mn];
struct node
{
	int sz;
	int ls, rs;
} trie[mn * 40 * 40];

int cnt;
void change(int &id, int l, int r, int num, int gai)
{
	if (id == 0)	// 空节点 新建
		id = ++cnt;

	trie[id].sz += gai;

    if (l == r)
		return;

    if (num <= mid)
		change(trie[id].ls, l, mid, num, gai);
	else
		change(trie[id].rs, mid + 1, r, num, gai);
}

int larquery(int id, int L, int l, int r)	// 求区间内 >= L 的数的数量
{
	if (l == r)
		return (L == l) ? trie[id].sz : 0;	// L = n + 1 造成的越界返回 0

	if (L <= mid)
		return trie[trie[id].rs].sz + larquery(trie[id].ls, L, l, mid);
	else
		return larquery(trie[id].rs, L, mid + 1, r);
}
int smaquery(int id, int R, int l, int r)	// 求区间内 <= R 的数的数量
{
	if (l == r)
		return (R == l) ? trie[id].sz : 0;	// R = 0 造成的越界返回 0

	if (R <= mid)
		return smaquery(trie[id].ls, R, l, mid);
	else
		return trie[trie[id].ls].sz + smaquery(trie[id].rs, R, mid + 1, r);
}

int main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		p[a[i]] = i;	// 记录每个数对应的位置
	}

	ll ans = 0;
	for (int i = 1; i <= n; i++)
	{
		for (int j = i; j > 0; j -= j & -j)		// 1 ~ (i - 1) 区间内的树包含的 > a[i] 的数的总量
			ans += (ll)larquery(tr[j], a[i] + 1, 1, n);
		for (int j = i; j <= n; j += j & -j)	// (i + 1) ~ n 区间内的树 插入这个数
			change(tr[j], 1, n, a[i], 1);
	}

	while (m--)
	{
		printf("%lld\n", ans);

		int x, LL = 0, RR = 0;
		scanf("%d", &x);

		for (int i = p[x]; i > 0; i -= i & -i)	// 1 ~ (i - 1) 区间的树包含的 > x 的数的总量
			LL += larquery(tr[i], x + 1, 1, n);

		for (int i = n; i > 0; i -= i & -i)		// 当前序列 < x 的数的总量
			RR += smaquery(tr[i], x - 1, 1, n);
		for (int i = p[x]; i > 0; i -= i & -i)	// 1 ~ p[x] 区间的树 < x 的数的总量
			RR -= smaquery(tr[i], x - 1, 1, n);
		// 两总量相减 = (p[x] + 1) ~ n 区间内 < x 的数的总量

        ans -= (ll)(LL + RR);	// ans -= 数 x 造成的前后逆序对数
        for (int i = p[x]; i <= n; i += i & -i)	// (i + 1) ~ n 区间内的树 删除这个数
			change(tr[i], 1, n, x, -1);
	}

	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值