题意:给出一个序列,如果某一项比它前面的项小(本来应该是一次增大的),这就是一组逆序项,如例:1 3 6 9 0 8 5 7 4 2就有22组逆序项。(1,0)(3,0)(3,2)(6,0)(6,5)(6,4)(9,0)(9,8)(9,5)。。。现在你可以将x1移至最后,再将x2移至最后,直到把xn-1移到最后(xn移到最后无意义),在这过程中,逆序项数目最少为多少?
分析:思路是先求出原序列的逆序项数目,再通过递推求出所有情况下逆序项数目。
先建立一棵空树,每段的sum都为0,输入每一项(如x1)时可查询树中已存在且比它大的数的个数(因为再它之前输入的是它前面的项),即这一项的逆序项的个数,再将这一项插入树中,更新树(为下次查询),一直到输入结束,此时累加起来的和即是这个序列的逆序项个数总和。然后递推求将所有情况下逆序项。记m为当前(原)序列的逆序项个数,将第一项x1移至最后时,此时逆序项个数为m-x1+((n-1)-x1),假设x1等于0,将0移到序列最后是原来的逆序项减去0(因为后面没有比0小的项),再加上((n-1)-0)(因为0放在最后时,0前面(n-1)个数都是逆序数),同理x2、x3、x4。。。
代码:
#include<cstdio> #include<algorithm> #define lson l, m, rt<<1 #define rson m + 1, r, rt<<1|1 using namespace std; const int maxn = 5555; int sum[maxn<<2]; int x[maxn]; void PushUP(int rt){ sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } /******************************** 建树,初始化sum值全为0,没有节点 *********************************/ void build(int l ,int r, int rt){ sum[rt] = 0; if(l == r) return; int m = (r + l)>>1; build(lson); build(rson); } /****************************************** 插入节点p,更新所有与p有关的sum ******************************************/ void update(int p, int l, int r, int rt){ if(l == r){ sum[rt]++; return; } int m = (l + r)>>1; if(p <= m) update(p, lson); else update(p, rson); PushUP(rt); } /******************************************* 查询L到R之间存在节点的个数 题意是查询大于L的数的个数,即默认R为n-1 *******************************************/ int query(int L, int R, int l, int r, int rt){ if(L <= l && r <= R) return sum[rt]; int m = (l + r)>>1; int ret = 0; if(L <= m) ret += query(L, R, lson); if(R > m) ret += query(L, R, rson); return ret; } int main(){ int n; while(~scanf("%d", &n)){ build(0, n - 1, 0); int m = 0; for(int i = 0; i < n; i++){ scanf("%d", &x[i]); m += query(x[i], n - 1, 0, n - 1, 1);//计算初始序列所有逆序数的个数m update(x[i], 0, n - 1, 1); } int ans = m; for(int i = 0; i < n; i++){ m += ((n - 1) - x[i]) - x[i];//当把x[i]移至最后,m会减少x[i],会增加((n - 1) - x[i]) ans = min(ans, m); } printf("%d\n", ans); } return 0; }
线段树求解Minimum Inversion Number
时间: 2024-11-10 01:18:21