[hdu 1394]Minimum Inversion Number(求逆序对)


本文最后更新于:2017年9月2日 下午

题目大意

求循环移动大小为n的数组后得到n个数组中的最小逆序对数(数组中数小于n)
传送门

a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)

思路:

用$O(n logn)$复杂度求出原数组逆序数(利用类似桶排序的思想,从1到n,每加入一个数,就在sum[a[i]]处+1,用一个数每次加上sum[a[i]+1]~sum[n],遍历后此数就代表了原数组逆序对数(可以用线段树,zkw,BIT等维护))后,就可以用O(1)的复杂度分别递推出其他解(每循环移动1个,逆序对数增加n-a[i],减少a[i]+1)

PS

由于me喜欢从1到n,而题中数据从0到n-1,me给a[i]都自增1,O(1)推式子时变成了增加n-a[i],减少a[i]-1(由于自增的缘故,需少减2)

Code

普通线段树

Time: 78ms

//hdu 1394
#include<iostream>
#include<cstdio>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define min(a,b) ((a)<(b)?(a):(b))
#define cls(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=2e5+10;
int sum[maxn<<2]={0},a[maxn]={0},n;
void update(int,int,int,int,int);
int query(int,int,int,int,int);
void PushUP(int);
int main()
{
	while(~scanf("%d",&n))
	{
		cls(sum,0);
		int s=0;
		for(int i=1;i<=n;i++) 
		{
			scanf("%d",&i[a]);
			i[a]++;//因为数中有0,都加上1。 
			s+=query(a[i]+1,n,1,n,1);
			update(a[i],1,1,n,1);
		}
		int mi=s;
		for(int i=1;i<n;i++)
		{
			mi-=a[i]-1+a[i]-n;
			s=min(s,mi);
		}
		printf("%d\n",s);
	}
	return 0;
}
void update(int p,int val,int l,int r,int rt)
{
	if(l==r)
	{
		sum[rt]+=val;
		return;
	}
	int m=(l+r)>>1;
	if(p<=m) update(p,val,lson);
	else update(p,val,rson);
	PushUP(rt);
}
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,ret=0;
	if(L<=m) ret+=query(L,R,lson);
	if(R>m) ret+=query(L,R,rson);
	return ret;
}
void PushUP(int rt)
{
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}

zkw线段树(非递归线段树)

Time: 62ms

//spaceskynet 2017-09-02 zkw hdu 1394
#include<iostream>
#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))
#define cls(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=2e4+10;
int sum[maxn<<1],a[maxn],n,M=1;
void update(int,int);
int query(int,int);
int main()
{
	while(~scanf("%d",&n))
	{
		cls(sum,0);
		int s=0;
		for(M=1;M<=n+1;M<<=1);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			a[i]++;
			s+=query(a[i]+1,n);
			update(a[i],1);
		}
		int mi=s;
		for(int i=1;i<n;i++)
		{
			mi-=a[i]-1+a[i]-n;
			s=min(s,mi);
		}
		printf("%d\n",s);
	}
	return 0;
}
void update(int p,int val)
{
	sum[p+=M]+=val;
	while(p)
	{
		p>>=1;
		sum[p]=sum[p<<1]+sum[p<<1|1];
	}
}
int query(int l,int r)
{
	int ret=0;
	for(l+=M-1,r+=M+1;l^r^1;l>>=1,r>>=1)
	{
		if(~l&1) ret+=sum[l^1];
		if(r&1) ret+=sum[r^1];
	}
	return ret;
}

BIT

Time: 31ms

//spaceskynet 2017-09-02 BIT
#include<iostream>
#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))
#define cls(a,b) memset(a,b,sizeof(a))
#define lowbit(x) ((x)&(-(x))) 
using namespace std;
const int maxn=6e3+10;
int sum[maxn]={0},a[maxn]={0},n;
void update(int,int);
int query(int);
int getsum(int,int);
int main()
{
	while(~scanf("%d",&n))
	{
		cls(sum,0);
		int s=0;
		for(int i=1;i<=n;i++) 
		{
			scanf("%d",&a[i]);
			a[i]++;//因为数中有0,都加上1。 
			s+=getsum(a[i]+1,n);
			update(a[i],1);
		}
		int mi=s;
		for(int i=1;i<n;i++)
		{
			mi-=a[i]-1+a[i]-n;
			s=min(s,mi);
		}
		printf("%d\n",s);
	}
	return 0;
}
void update(int p,int val)
{
	for(int i=p;i<=n;i+=lowbit(i))
	{
		sum[i]+=val;
	}
}
int query(int p)
{
	int ret=0;
	for(int i=p;i>0;i-=lowbit(i))
	{
		ret+=sum[i];
	}
	return ret;
}
int getsum(int l,int r)
{
	return query(r)-query(l-1);
}

总结

总的来说,$普通线段树>zkw线段树>BIT$。
所以说,常数较小的BIT是不错的选择(毕竟加上差分,线段树的题,BIT也可以完成大部分了)。

**%%BIT**

文章作者: SpaceSkyNet
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SpaceSkyNet !
  目录
评论