HDU 1394 求逆序数

先贴一个TLE的代码...

#include<iostream>
#include<cstdio>
#define MAXN 11111
using namespace std;

int gt[MAXN<<2],st[MAXN<<2];
int date[MAXN];

void pushUpmax( int rt ){
 	 gt[rt]=max(gt[rt<<1],gt[rt<<1|1]);
}
void pushUpmin( int rt ){
 	 st[rt]=min(st[rt<<1],st[rt<<1|1]);
}

void build( int l,int r,int rt )
{
 	 if( l==r )
 	 {
	  	 gt[rt]=st[rt]=date[l];
	  	 return ;
	 }
	 int m=(l+r)/2;
	 build( l,m,rt<<1 );
	 build( m+1,r,rt<<1|1 );
	 pushUpmax(rt);
	 pushUpmin(rt);
}

int querygt( int v,int l,int r,int rt )//在最大值树上查找[l,r]区间内小于v的节点数。 
{
 	if( l==r )
 		return 0;
 	if( gt[rt]<v )
 		return r-l+1;
	int m=(l+r)/2;
	int ret=0;
	ret+=querygt( v,l,m,rt<<1 );
	ret+=querygt( v,m+1,r,rt<<1|1 );
	return ret;
}

int queryst( int v,int l,int r,int rt )//在最小值树上查找[l,r]区间内大于v的节点数 
{
 	if( l==r )
 		return 0;
 	if( gt[rt]>v )
 		return r-l+1;
	int m=(l+r)/2;
	int ret=0;
	ret+=queryst( v,l,m,rt<<1 );
	ret+=queryst( v,m+1,r,rt<<1|1 );
	return ret;
}

int main()
{
 	int N;
 	while( scanf("%d",&N)!=EOF )
 	{
	 	   for( int i=1;i<=N;i++ )
	 	   {
		   		scanf( "%d",&date[i] );
		   		date[i+N]=date[i];
		   }
		   build(1,2*N,1);
		   int ans=0;
		   for( int i=1;i<=N;i++ )
		   for( int j=i+1;j<=N;j++ )
		   		if( date[i]>date[j] )
		   			ans++;
		   for( int i=1;i<=N;i++ )
		   		ans=min( ans,ans+queryst(date[i],i+1,i+N-1,1)-querygt(date[i],i+1,i+N-1,1) );
		   
		   printf( "%d\n",ans );
  	}
 	return 0;
}

虽然TLE了但是是我的一次尝试。虽然TLE了... 无奈..

首先定义逆序数:

在一串数字序列中i<j时,Ai>Aj的个数。反之,i>j时,Ai<Aj的个数.

通俗的讲就是Ai之前比Ai大的的数字个数.

下面简要叙述一下我的公式吧。

|.......|AB|......|

假设AB是串中的两个临近的数,将这两个数换位置,逆序数会如何改变呢?

|.......|BA|......|

if(A>B) r--;

if(B>A) r++;

显然AB的换位和左右两边这些个东西: |......| 是没有关系的。

那么A|......|换位成为|......|A呢?

我们可以分步看...

ABCDEFG

BACDEFG

BCADEFG

BCDAEFG

BCDEAFG

BCDEFAG

BCDEFGA

这样将A移到最后。

那么这个过程的逆序数是怎样变化的呢?

简单推知A增加的逆序数为[B,G]中大于A的。A减少的逆序数为[B,G]中小于A的。

so... R+=[B,G]中大于A的-[B,G]中小于A的。

R为原逆序数。

这就是上面的代码的由来...

很可惜,TLE了...

在题目中有特殊性质。数字有N个为[0,N-1]各有一个。

于是乎将Ai移到最后,其中大于Ai的有(N-Ai-1)个,小于Ai的有Ai个,于是

R+=(N-Ai-1)-Ai;

这便是一次变化的公式。

so...

暴力来做吧...

#include<iostream>
using namespace std;

int main()
{
 	int N;
 	while( scanf( "%d",&N )!=EOF )
 	{
	 	   int date[5555],ans=0;
	 	   for( int i=0;i<N;i++ )
	 	   		scanf( "%d",&date[i] );
	 	   for( int i=0;i<N;i++ )
	 	   for( int j=0;j<i;j++ )
	 	   		if( date[j]>date[i] )
	 	   			ans++;
	 	   int temp=ans;
	 	   for( int i=0;i<N;i++ )
	 	   {
		   		temp=temp-date[i]+(N-date[i]-1);
	 	   		ans=min( ans,temp );
		   }
	 	   printf("%d\n",ans );
  	}
  	return 0;
}

树状数组:

#include<iostream>
using namespace std;

int tree[5555],N;

void update( int pt,int v )
{
 	 while( pt<=N )
 	 {
	  		tree[pt]+=v;
	  		pt+=pt&(-pt);
	 }
}

int find( int pt )
{
 	int ret=0;
 	while( pt ) 
	{
 	 	   ret+=tree[pt];
 	 	   pt-=pt&(-pt);
    }
 	return ret;
}

int main()
{
 	int date[5555];
 	while( scanf("%d",&N)!=EOF )
 	{
	 	   memset( tree,0,sizeof(tree) );
	 	   for( int i=0;i<N;i++ )
	 	   {
	 	   		scanf( "%d",&date[i] );
	 	   		date[i]++;
		   }
	 	   int ans=0;
		   for( int i=N-1;i>=0;i-- )
	 	   {
		   		ans+=find(date[i]);
		   		update(date[i],1);
		   }
		   printf( "%d\n",ans ); 
		   int temp=ans;
		   for( int i=0;i<N;i++ )
		   {
		   		date[i]--;
		   		temp=temp-date[i]+(N-date[i]-1);
		   		ans=min(temp,ans);
   		   }
   		   printf( "%d\n",ans );
  	}
  	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值