算法分析
Trie + 位运算
先挂个代码,明天再写(已经在代码旁边加注释了 可参考
AC Code
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
typedef long long ll;
const int MAXN = 3e5 + 5;
int t[32 * MAXN][2];
vector<int> g[32 * MAXN];
int dp[32][2];
int cnt = 0;
void add(int num, int pos)//将 num 插在第 pos 个结点上
{
int u = 0;
for (int i = 30; i >= 0; i --)//从高到低枚举每一位
{
int bit = ((num >> i) & 1);
if (!t[u][bit]) t[u][bit] = ++ cnt;//还没填的话
u = t[u][bit];
g[u].push_back(pos);
}
}
void cal(int u, int b)
{
int L = t[u][0], R = t[u][1];
if (L) cal(L, b - 1);
if (R) cal(R, b - 1);
int res = 0;
int i = 0;
for (int x : g[L])//遍历左子树中的点 => 存的是 a[i] 的下标
{
//左子树中的结点值 一定是小于 右子树的
//因此枚举右子树中的结点,如果出现了 左子树中的下标 大于 右子树中的结点的 那么就是出现了逆序对
while (i < g[R].size() && x > g[R][i]) i ++;
res += i;
}
// 对于 在 b 这一层的 左子树的贡献为 res
// 那么转换过去 在 右子树中的逆序对会是总数 - res
dp[b][0] += res;
dp[b][1] += (ll)g[L].size() * (ll)g[R].size() - res;
}
int n, a[MAXN];
void solve()
{
scanf("%lld",&n);
for (int i = 1; i <= n; i++)
{
scanf("%lld",&a[i]);
add(a[i], i);//将 a[i] 这个数插在第 i 个节点上
}
cal(0, 30);
int inv = 0, ans = 0;
for (int i = 0; i <= 30; i ++)
{
inv += min(dp[i][0], dp[i][1]);//最少的逆序对
// 如果右侧的逆序对要少的话 那就需要将 这一位反转一下
// 反转的办法就是 在这一位补上 1
// 那么右侧的 1 就会变成 0,数字变小减少逆序对数目
if (dp[i][1] < dp[i][0])ans += (1 << i);
}
printf("%lld %lld\n",inv,ans);
}
signed main()
{
solve();
}