/*
線段樹求逆序數 求小逆序數 神奇
題意:給定一個序列,對該序列的n種排列(排列如下)的每種排列(0 ~ n-1)的逆序數求最小值:
a1, a2, ..., an-1, an
a2, a3, ..., an, a1
a3, a4, ..., an, a1, a2
...
an, a1, a2, ..., an-1
思路:先求出初始序列的逆序數,可以歸併,這裡用的是線段數求。
設當前逆序數為sum,則每次把第一個數x移到最後,則新序列的逆序數 = sum - x + (n - 1 - x)
sum = sum - a[i] + (n - 1 - a[i]); //太神奇了,這個轉移方程.
前面部分用線段樹求初始逆序簡單說一下,就是先建一棵樹,每個節點[l,r]儲存一個sum值,表示到目前為止[l,r]出現的個數。如當前序列為1,3, 則節點[0,3].sum = 2, [0, 4].sum = 2。然後每次掃到一個新的數x都先詢問舊序列中[x+1, n-1]中出現的個數...
*** 逐個插入值(即輸入的一個值); 在每插入一個值後就更新包含該區間的所有的數的個數(加一)***
*/
#include <stdio.h>#include <algorithm>using namespace std;#define debug printf("!\n")#define MAXN 5005#define L(x) ((x)<<1)#define R(x) (((x)<<1)|1)#define MID(x, y) (((x)+(y))>>1)int a[MAXN], pos[MAXN], high[MAXN];struct Node { int l, r, sum;} f[MAXN * 3];int n;void Update(int root){ if(root == 0) return ; f[root].sum++; Update(root >> 1);}int Query(int root, int l, int r){ if(l <= f[root].l && f[root].r <= r) { return f[root].sum; } int mid = MID(f[root].l, f[root].r); if(r <= mid) return Query(L(root), l, r); else if(mid < l) return Query(R(root), l, r); else return Query(L(root), l, mid) + Query(R(root), mid+1, r);}void build(int root, int l, int r){ f[root].l = l, f[root].r = r, f[root].sum = 0; if(l == r) { pos[l] = root; return ; } int mid = MID(l, r); build(L(root), l, mid); build(R(root), mid+1, r);}int main(){ int x; while(scanf("%d", &n) != EOF) { build(1, 0, n-1); for(int i = 0; i < n; i++) { scanf("%d", &x); a[i] = x; high[x] = Query(1, x, n-1); Update(pos[x]); } int sum = 0; for(int i = 0; i < n; i++) sum += high[i]; int ans = sum; for(int i = 0; i < n; i++) { sum = sum - a[i] + (n - 1 - a[i]); //太神奇了,這個轉移方程. ans = min(ans, sum); } printf("%d\n", ans); }}