HDU 1394 暴力 / 线段树

5 篇文章 0 订阅
4 篇文章 0 订阅

题意:给你一个n个数的序列,其中组成的数只有0-n,我们可以进行这么一种操作:把第一个数移到最后一个,次数不限。问,在原始数列和最新生成的数列中逆序数最小可以是多少?

暴力是可以过的。当然是,只暴力求原序列的逆序对,通过递推求新序列的逆序对数。

递推关系是这样子的:首先要明白将第一个数移到最后一个,其逆序数的变化是在前一个逆序数n的基础上这样

变成n-low(a[1])+up(a[1])。因为这是一个0 ~ n-1地全排列,所以low(a[1])为a[1]个,up(a[1])为n-a[1]个。

指路:递推的解释

或者这样说,我把第一个数删掉,逆序数减少low(a[1])个;再把第一个数放到最后,逆序数增加up(a[1])个。

 

法1:暴力

//暴力也可过 
//HDU 1394
#include<iostream>
#include<algorithm>
#include<set>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<stdio.h>
using namespace std;
#define ll long long
const int maxn=5005;
int num[maxn];
int sum[maxn];
int n;
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		memset(num,0,sizeof(num));
		memset(sum,0,sizeof(sum));
		int sum=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&num[i]);
			for(int j=1;j<i;j++)
			{
				if(num[j]>num[i])
				{
					sum++;
				}	
			}
		}

		int ans=sum;
		for(int i=1;i<=n-1;i++)
		{
			sum=sum-num[i]+(n-num[i]-1);
	
			ans=min(ans,sum);
		}
	
		printf("%d\n",ans);
		
	}
}

 

法2: 线段树

开始,并不能理解,这个题目也和线段树有关?

然后,翻了题解。

建立线段树的过程,跟逆序对完全没有关系!

只不过,每插入一个数据,通过线段树进行查询比这个数据大的有多少,也就是能 增加多少逆序对。 

所以,线段树,初始化所有结点为0。标号范围在1-n的结点 i 的tree[i],就代表数据i出现了几次。(当然,叶子节点肯定最多只有一次)。

每次更新,也就是对应的tree[]++;

父节点结点代表的区间[a,b]代表数据a-b,一共出现了几次。

//AC
//HDU 1394 线段树
#include<iostream>
#include<algorithm>
#include<set>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<stdio.h>
using namespace std;
#define ll long long
const int maxn=5005;
int num[maxn];
int tree[maxn<<2];

int n;
//每个结点代表逆序对数 
void pushup(int root)
{
	tree[root]=tree[2*root]+tree[2*root+1];
	return;
 } 

//开始全部初始化为0 
void build(int root,int left,int right)
{
	if(left==right)
	{
		tree[root]=0;
		return; 
	}
	int mid=(left+right)/2;
	build(2*root,left,mid);
	build(2*root+1,mid+1,right);
	pushup(root);
	return;
}

//单点更新
void update(int root,int index,int left,int right)
{
//	cout<<'u'<<left<<" "<<right<<" "<<tree[root]<<endl;
	if(left==right)
	{
		//if(index==left)这句话一定不能加,我这是从哪想的啊?? 
			tree[root]++;
			return;
		
	}
	int mid=(left+right)/2;
	if(index<=mid)//这个index总是忘掉,所以每次测试总会死循环! 
		update(2*root,index,left,mid);
	else if(index>mid)
		update(2*root+1,index,mid+1,right);
	pushup(root);
 } 

//查询
int query(int root,int left,int right,int qleft,int qright)
{
//	cout<<'q';
	if(right<qleft || left>qright)
		return 0;
	if(left>=qleft && right<=qright)
		return tree[root];
	int mid=(left+right)/2;
	int ans=0;
	if(mid>=qleft)
		ans+=query(2*root,left,mid,qleft,qright);
	if(mid<qright)
		ans+=query(2*root+1,mid+1,right,qleft,qright);
	return ans;			
} 
 

int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		build(1,1,n);
		int sum=0;
		for(int i=0;i<n;i++)
		{
			scanf("%d",&num[i]);
			//以1-n进行存储 
			sum+=query(1,1,n,num[i]+1,n);//查询已经存储过的,比它大的有多少
			update(1,num[i]+1,1,n); 	
		}
	
		int ans=sum;
	//	cout<<ans<<" ";
		for(int i=0;i<n-1;i++)
		{
			sum=sum-num[i]+(n-num[i]-1);
			if(sum<0) continue;//不过这句话,好像存在什么WA点。 
			ans=min(ans,sum);
		}
		cout<<ans<<endl;
	}
	
	
}

 

最后,自己在update()函数,总是会出现错误。

if(index==left) ,真是不知道自己从哪里想到的?突然有点不明白,为什么不能加?是index是num[ ]的下标,根本不是tree[ ]的下标吗?

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值