XOR Inverse(trie + 逆序对)

9 篇文章 0 订阅
2 篇文章 0 订阅

Solution One

  1. d p [ i ] [ j ] : x 二 进 制 下 的 倒 数 第 i 位 为 j 时 , 逆 序 对 的 数 量 dp[i][j]: x二进制下的倒数第i位为j时,逆序对的数量 dp[i][j]:xij

  2. 对于一棵trie树,插入的字符串是一个数的二进制字符串(从高位开始插入)。其结构其实就是一棵二叉树。假如,从0到 2n - 1依次插入建立一棵trie树,最终就是一棵n + 1层的完全二叉树。对于本题中,仅仅是完全二叉树的部分节点。
    在这里插入图片描述

  3. 将每个数字从高位到低位建立一棵trie树,建树过程中,记录每个结点经过的次数cnt[p]。

  4. 显然,经过左子树的数小于经过右子树的数。

  5. 对于第i个数字a[i]的第j位(该结点的编号为x,其兄弟结点的编号为y): 如果第j位是0,那么当X的第j位也是0时,逆序对数量的贡献为前i - 1个数子经过结点y的次数,也即cnt[y];如果a[i]的第j为是1,那么当X的第j位也是1时,逆序对的数量就是cnt[x]。

  6. X的第j位为0时,字典树第j层的逆序对总数不变;第j位为1时,第j层的所以左右子树互换。

Code One

const int N = 3e5 + 5, M = 5e6 +7;

ll dp[35][2];

int t[M][2], tot, cnt[M];
void insert(int x)
{
	int p = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = x >> i & 1;
		if (t[p][c] == 0) t[p][c] = ++tot;
		dp[i][c] += cnt[t[p][c ^ 1]];
		p = t[p][c]; cnt[p]++;
	}
}

int main()
{
	IOS;
	int n; cin >> n;
	for (int i = 1, x; i <= n; i++)
	{
		cin >> x;
		insert(x);
	}

	ll inv = 0, x = 0;
	for (int i = 0; i <= 30; i++)
	{
		inv += min(dp[i][0], dp[i][1]);
		if (dp[i][1] < dp[i][0]) x += 1 << i;
	}
	cout << inv << " " << x << endl;


	return 0;
}

Solution Two

在建树的过程中,对于a[i]经过的每个结点都存入其下标i。最后,对trie树遍历,对于每个结点,经过其左子树的数都小于经过其右子树的数。假设:左子树下标集合为 L, 右子树下标集合为R,x为左子树中的一个下标,y为右子树中的一个下标:那么这两个集合中逆序对的数量就是,偏序对<x, y>的数量(偏序对满足x > y)

Code Two

此方法明显比方法1慢许多,且空间也比较大。慢的地方在于计算逆序对数量的方法不同。二者的思想都类似与二分归并找逆序对,但后者属于是二分里面暴力计算,前者比较正宗,O(1)求得结果。

const int N = 3e5 + 5, M = 5e6 +7;

ll dp[35][2];

int t[M][2], tot;
vector<int> v[M];
void insert(int x, int i)
{
	int p = 0;
	for (int j = 30; j >= 0; j--)
	{
		int c = x >> j & 1;
		if (t[p][c] == 0) t[p][c] = ++tot;
		p = t[p][c];
		v[p].push_back(i);
	}
}

void dfs(int p, int k )
{
	int l = t[p][0], r = t[p][1];
	if (l) dfs(l, k - 1);
	if (r) dfs(r, k - 1);
	if (l == 0 || r == 0) return;

	ll inv = 0;
	int i = 0;
	for (auto x : v[l])
	{
		while (i < sz(v[r]) && v[r][i] < x) i++;
		inv += i;
	}
	dp[k][0] += inv;
	dp[k][1] += (ll)sz(v[l]) * sz(v[r]) - inv;
}

int main()
{
	IOS;
	int n; cin >> n;
	for (int i = 1, x; i <= n; i++)
	{
		cin >> x;
		insert(x, i);
	}

	dfs(0, 30);

	ll inv = 0, x = 0;
	for (int i = 0; i <= 30; i++)
	{
		inv += min(dp[i][0], dp[i][1]);
		if (dp[i][1] < dp[i][0]) x += 1 << i;
	}
	cout << inv << " " << x << endl;


	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

to cling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值