Solution One
-
d p [ i ] [ j ] : x 二 进 制 下 的 倒 数 第 i 位 为 j 时 , 逆 序 对 的 数 量 dp[i][j]: x二进制下的倒数第i位为j时,逆序对的数量 dp[i][j]:x二进制下的倒数第i位为j时,逆序对的数量
-
对于一棵trie树,插入的字符串是一个数的二进制字符串(从高位开始插入)。其结构其实就是一棵二叉树。假如,从0到 2n - 1依次插入建立一棵trie树,最终就是一棵n + 1层的完全二叉树。对于本题中,仅仅是完全二叉树的部分节点。
-
将每个数字从高位到低位建立一棵trie树,建树过程中,记录每个结点经过的次数cnt[p]。
-
显然,经过左子树的数小于经过右子树的数。
-
对于第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]。
-
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;
}