POJ 2299 Ultra-QuickSort (树状数组、归并排序)

题目链接:http://poj.org/problem?id=2299

做法一:归并排序求逆序对

归并排序求逆序对的做法很经典,在归并排序中,如果 left<=i<=mid  和    mid+1 <=j<=right,(i<j) ,那么,如果 a[i]<=a[j],这并不产生逆序对,但是,如果a[i]>a[j],如果在mid之前有大于k个大于a[j]的数,那么ans+=k,k是多少呢?k=mid-i+1!为什么?你想想,因为a[i]>a[j],因为 i~~mid已经排序好了,就是说a[i]<a[i+1]<a[i+2]<....<a[mid],就是说i~mid的数都可以与j形成逆序对,证明完毕,k=mid-i+1。在每次合并的时候 ,如果a[i]>a[j],ans+=k即可。

贴上渣渣代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <cstring>
#include <map>
#include <set>

#define LL long long
#define pb push_back
#define pf push_front
#define loop(a,b,c) for(int a=b;a<=c;a++)
#define rloop(a,b,c) for(int a=b;a>=c;a--)
#define clr(a,b) memset(a,b,sizeof a)
#define inf 1<<30
#define x first
#define y second
#define maxn 500005

using namespace std;
int a[maxn],n;
LL ans;
void merge(int l,int r)
{
	int *b = new int[maxn];
	int m = (l+r)/2,i,j,len=0;
	i=l;
	j=m+1;
	while(i<=m&&j<=r)
	{
		if(a[i]<=a[j]) b[len++] = a[i++];
		else b[len++] = a[j++],ans+=m-i+1;
	}
	while(i<=m) b[len++] = a[i++];
	while(j<=r) b[len++] = a[j++];
	loop(i,0,len-1) a[l+i] = b[i];
        delete []b;
}
void merge_sort(int l,int r)
{
	if(l>=r) return;
	int m = (l+r)/2;
	merge_sort(l,m);
	merge_sort(m+1,r);
	merge(l,r);
}
void solve()
{
	loop(i,1,n)
		scanf("%d",&a[i]);
	ans=0;
	merge_sort(1,n);
	printf("%lld\n",ans);
}
int main()
{
	//freopen("data.txt","r",stdin);
	while(scanf("%d",&n)&&n)
	solve();
}

额,跑了1069MS


做法二:树状数组

当我听说可以用树状数组做的时候,确实有点摸不着头脑。。。完全没想到可以用树状数组去做(怪我渣咯)

怎么做呢?其实原理很简单!就取样例来说吧!

对于样例 n=5 ,a[1~n] = {9,1,0,5,4},不妨设d[j]表示 j 是否出现过,若是,标记为1

当i=1,d[9]=1,对0~9求和,即sum(9)=1,说明了有多少个数是不大于9的,那么i-sum(a[i])就表示有多少个数比a[i] 大了,在这里就是1-sum(9)=0

当i=2,d[1]=1,对0~1求和,sum(1)=1,那么在i=0~2就有2-sum(1)=1个数比2大

当i=3,d[0]=1,对0~0求和,sum(0)=1,那么在i=0~3就有3-sum(0)=2个数比1大

以此类推。。。

将上面的i-sum(a[i])加起来,就是答案了

但是,题目所给的数范围是0~999,999,999,根本没办法开一个d数组那么大!但是,观察到n最大才500,000,其实每个a[i]都可以映射成1~~500,000的每个数!

简而言之,就是要对数组进行离散化

还是对于样例n=5 ,a[1~n] = {9,1,0,5,4},我们用一个结构体(或者pair)来记录原输入的值大小以及它的下标,对于样例而言,就是

{(x,y)|(9,1),(1,2),(0,3),(5,4),(4,5),x是输入的值,y是下标}

对上面按x值大小排序得

{(x,y)|(0,3),(1,2),(4,5),(5,4),(9,1),x是输入的值,y是下标}

用一个b数组来存储离散化后的数组,即有b[x[i]]=i !!结果为 {5,2,1,4,3}

为什么要这样做呢?一开始我也有点懵逼,毕竟我是第一次接触离散化这个东西。

但是,只要认真想想就知道, 其实就是改变了x的值,把x的范围缩减到1~500,000的范围,然后变回原来的顺序而已。可以看出离散化后,数组中所有元素的大小关系是完全没有变化的!

然后我们就可以开一个足够大的数组去实现之前的算法了!

贴上代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <cstring>
#include <map>
#include <set>

#define LL long long
#define pb push_back
#define pf push_front
#define loop(a,b,c) for(int a=b;a<=c;a++)
#define rloop(a,b,c) for(int a=b;a>=c;a--)
#define clr(a,b) memset(a,b,sizeof a)
#define inf 1<<30
#define x first
#define y second
#define maxn 500005

using namespace std;
typedef pair<int,int> P;
int n,ans,c[maxn],d[maxn];
P a[maxn];
int lowbit(int x) {return x&(-x);}
void updata(int i,int x)
{
	while(i<=n)
	{
		c[i]+=x;
		i += lowbit(i);
	}
}
int sum(int i)
{
	int s = 0;
	while(i>0)
	{
		s += c[i];
		i -= lowbit(i);
	}
	return s;
}
void solve()
{
	loop(i,1,n)
	scanf("%d",&a[i].x),a[i].y=i;
	ans = 0;
	sort(a[i]+1,a[i]+n+1);
	loop(i,1,n) d[i]=a[i].y;
	// loop(i,1,n) printf("%d\n",d[i]);
	loop(i,1,n)
	{
		updata(d[i],1);
		ans+=i-sum(d[i]);
	}
	printf("%d\n",ans);
}
int main()
{
	//freopen("data.txt","r",stdin);
	while(scanf("%d",&n)&&n)
	solve();
}

运行了422MS,按道理说呢时间复杂度都是nlogn的,不过实际运行起来,相差还是蛮大的,难道跟归并排序的递归有关?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值