如在阅读本文时遇到不懂的部分,请在评论区询问,或跳转 线段树总介绍
线段树求逆序对数比较少见啊(归并排序多快啊...但是本文是讲解线段树写法...),何况这题还加了点别的玩意儿...
1. 本来这种题目要离散化的,可是体中保证了数列0~n-1.
2. 每次把首位放到最末,显然不能 每次都求逆序对 ,于是又到了推 倒 导时间。
由 a1 a2 ... an 变为 a2 a3 ... an a1 减少的逆序对数为 a2~an中比a1小的数的个数 增加的逆序对数为 a2~a1中比a1大的数的个数 因为a1~an的值是0~n-1 所以看其排名即可 即比a1小的有a1个数 比a1大的有n-a1+1个数 实际上代码中的MAXN=n-1
于是只要求出原序列的逆序对个数即可
逆序对
对于给定的数列 a1 ,a2 ,...,an :如果有 i,j 满足 i < j 且 ai > aj ,我们说 (ai ,aj ) 为一对逆序对。
逆序对的朴素求法
从 1 ∼ n 枚举数列中的每一个数 i,从 1 ∼ i 枚举数列中的每一个数 j,找出所有满足 a j > a i 的数对 (i,j)。
利用线段树优化
这个优化依赖于桶,如果题目不保证数列中的数的数值大小则需要离散化。按照数列的顺序将数插入 1 ∼ N 的桶中(N 表示桶排序后最大桶的序号),对于每一个 ai 统计 ai +1∼N 桶的和,累加即可。
也就是依次把每个数放入桶中并查询比他大的且比他先放(排在他前面)的有多少个数
代码
/*线段树+离散化+桶 求逆序对*/ /*每次往桶里加一个数并查询比它大的数个数*/ /*hdu1394*/ #include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int N=2e7+3; typedef long long LL; int v[N<<2],n; LL list[N],num[N],ans=0; //sum -> for the first seq #define ls (rt<<1) #define rs (ls|1) #define mid (l+r>>1) #define pushup(rt) v[rt]=v[ls]+v[rs] void build(int rt,int l,int r){ if(l==r){v[rt]=0;return;} build(ls,l,mid);build(rs,mid+1,r); pushup(rt); } void update(int rt,int l,int r,int x){ v[rt]++; if(x==r&&x==l)return; if(x<=mid)update(ls,l,mid,x); else update(rs,mid+1,r,x); //pushup(rt); return; } int query(int rt,int l,int r,int x,int y){ if(x<=l&&y>=r)return v[rt]; int res=0; if(x<=mid)res+=query(ls,l,mid,x,y); if(y>mid)res+=query(rs,mid+1,r,x,y); return res; } int main(){ while(scanf("%d",&n)!=EOF){ for(int i=1;i<=n;++i) scanf("%lld",&list[i]),list[i]++,num[i]=list[i]; build(1,1,n); ans=0; sort(num+1,num+1+n); int siz=unique(num+1,num+1+n)-num; int MAXN=n; /*int MAXN=siz-1; //注意取下标 for(int i=1;i<=n;++i) list[i]=lower_bound(num+1,num+siz,list[i])-num; //此时list内的数已离散化 //build(1,1,MAXN); */ for(int i=1;i<=n;++i){ update(1,1,MAXN,list[i]); //此数字多了一个 if(list[i]!=MAXN) ans+=query(1,1,MAXN,list[i]+1,MAXN); //不是最大查询比他大的 } LL sum=ans; for(int i=1;i<=n;++i){ //每次把最前一个放到末尾 int x=list[i]-1,y=MAXN-list[i]; //大的贡献 小的贡献 //x比他小的(-) y比他大的(+) sum+=y-x;ans=min(sum,ans); } printf("%lld ",ans); } return 0; }
End