题单链接
持续更新
单点更新
1、Minimum Inversion Number
题意
给了一个大小为n的数组,每个数的大小在0 - n-1之间,现在你可以执行n次操作,每一次将数组中的第一个数移动到最后一位,每执行一次操作,寻找当前数组的逆序对的大小,n次操作执行完后,让你找逆序数的最小数量是多少(逆序数:前面比它大的数的个数)。
思路
线段树
首先数组中的数都是1-n的(我们把每个数都+1),那么我们使用线段树来求逆序对,只需要每次将a[i]这个这个位置+1,然后求a[i]后面有多少个数是比它小的,这样就求到了逆序对的数目。(如果a[i]的范围不是1-n的话,我们可以进行离散化在求)。
接下来思考移动了第一个数后逆序对的变化:假设在x这个位置,x-n中比它大的数为m个,那么比它小的数有n-m-1个,那么当它移动到末尾的时候,之前比它大的数会和它构成逆序对,那么逆序对的数目会增加,但是之前比它小的,在移动之前跟它构成逆序对,移动了之后逆序对会减少,所以移动之后的逆序对数目是(cnt表示初始的逆序对数目):cnt - (n-m-1) + m;
#include <bits/stdc++.h>
#define ul u << 1
#define ur u << 1 | 1
using namespace std;
const int mod = 1e9 + 7, N = 1e5 + 10;
int n;
int a[N];
struct node
{
int l, r;
int sum;
}tr[N << 2];
void pushup(int u) { tr[u].sum = tr[ul].sum + tr[ur].sum; }
void build(int u, int l, int r)
{
tr[u] = {l, r, 0};
if (l == r) return ;
int mid = l + r >> 1;
build(ul, l, mid), build(ur, mid + 1, r);
}
void modify(int u, int pos)
{
if (tr[u].l == pos && tr[u].r == pos) { tr[u].sum = 1; return; }
int mid = tr[u].l + tr[u].r >> 1;
if (pos <= mid) modify(ul, pos);
else modify(ur, pos);
pushup(u);
}
int query(int u, int l, int r)///查询l-r之间有多少个数是比它小的。
{
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
int ans = 0, mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) ans = query(ul, l, r);
if (r > mid) ans += query(ur, l, r);
return ans;
}
void solve()
{
int ans = 0x3f3f3f3f, res = 0;
build(1, 1, n);
for (int i = 1; i <= n; i ++)
{
scanf("%d", &a[i]); a[i] += 1;
modify(1, a[i]);///在a[i]这个位置加上1。
///每次修改了之后,查询这个数后面有多少个数是比它小的,这样就找到了逆序对
int cnt = query(1, a[i] + 1, n);
res += cnt;///求到原始顺序的逆序对
}
for (int i = 1; i <= n; i ++)
{
res = res - (a[i] - 1) + n - a[i];
ans = min(ans, res);
}
printf("%d\n", ans);
}
signed main()
{
while (~scanf("%d", &n)) solve();
return 0;
}