POJ2299Ultra-QuickSort (线段树和归并排序的解法)
题目大意就是说帮你给一些(n个)乱序的数,让你求冒泡排序需要交换数的次数(n<=500000)
此题最初真不会做,我也只是在听了章爷的讲解后才慢慢明白过来的
首先介绍线段树的解法:
我们先将原数组每个值附上一个序号index,再将它排序。如题目的例子:
num: 9 1 0 5 4
index: 1 2 3 4 5
排序后:
num: 0 1 4 5 9
index: 3 2 5 4 1
然后由于排序后num为0的点排在原来数组的第3个,所以为了将它排到第一个去,那就至少需要向前移动两次,同时它也等价于最小的数0之前有2个数比它大(所以要移动两次),将0移到它自己的位置后,我们将0删掉(目的是为了不对后面产生影响)。再看第二大的数1,它出现在原数组的第二个,他之前有一个数比它大所以需要移动一次。这样一直循环下去那么着5个数所需要移动的次数就是:
num: 0 1 4 5 9
次数 2 1 2 1 0
将次数全部要加起来就是最后所需要移动的总次数。
方法章爷是已经告诉我了,但是最初我一直是觉得不好实现。到后来才慢慢、慢慢弄好。方法就是在建一棵树时,不是直接将原来的num放进树里面,而是将它的下标放进树里面,最初每个节点上赋值为1.然后当查找第一个num时,由于是找的下标为3的位置,所以我们就直接找区间[1,3)之间有多少个1(就是求前导和),这里面1的个数就是第一个num=0索要移动的次数,然后我们把0去掉,其实也就是吧下标为3的那个1去掉。这样每个值就依次计算出来了。
当然其实只要是想明白了,不用线段树,直接用树状数组写起来会简便很多。(因为每次只需要计算前导和以及去掉某一个点,是对点的操作)。
这里再讲一下归并排序的方法(对于最基础就没有掌握好的我来说听到他们说归并排序可以解题时,我竟然一团雾水,竟然连归并排序都忘记了),看了一下归并排序的实现过程,其实马上就可以找到思路,由于本题实际上就是要求逆序对(即满足i<j,a[i]>a[j]的数对)的个数。而我们再回顾一下归并排序的过程:
假设回溯到某一步,后面的两部分已经排好序(就是说当前需要归并的两个部分都是分别有序的),假设这两个序列为
序列a1:2 3 5 9
序列a2:1 4 6 8
此时我们的目的就是要将a1和a2合并为一个序列。
由于在没排序前a2序列一定全部都是在a1序列之后的,当我们比较a2的1与a1的2时,发现1<2按照归并的思想就会先记录下a2的1,而这里实际上就是对冒泡排序的优化,冒泡是将a2的1依次与a1的9,5,3,2交换就需要4次,而归并却只有一次就完成了,要怎么去记录这个4呢,实际上由于1比2小而2后面还有4个数,也就是说那我的结果就必须要+4,也就是记录a1序列找到第一个比a2某一个大的数,他后面还余下的数的个数就是要交换的次数。
同时我们看a2的4时,a1中第一个比它大的数是5,5之后共有两个数,那结果就+2,。依次下去就可以计算出结果。但是由于我们任然没有改变归并排序的过程。所以复杂度还是O(nlogn),比上面的线段树要快。
另外,此题有一坑就是结果会超int32,,用__int64
首先是线段树的解法11892 KB 1047 ms
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define mem(a) memset(a,0,sizeof(a))
#define MIN(a , b) ((a) < (b) ? (a) : (b))
#define MAXN 500010
#define INF 1000000007
#define lson k<<1, l, mid
#define rson (k<<1)|1, mid+1, r using namespace std; int Tree[MAXN<<], index[MAXN], num[MAXN];
int N; int cmp(const int i, const int j)
{
return num[i] < num[j];
} void Edit(int k, int l, int r, int num, int value)
{
Tree[k] += value;
if(r==l) return ;
int mid = (l+r) >> ;
if(num <= mid) Edit(lson, num, value);
else Edit(rson, num, value);
} int Search(int k, int l, int r, int L, int R)//L,R是要找的区间
{
if(L<=l && r<=R) return Tree[k];
int mid = (l+r) >> ;
int ans = ;
if(L <= mid) ans += Search(lson, L, R);
if(R > mid) ans += Search(rson, L, R);
return ans;
} int main()
{
while(~scanf("%d", &N) && N)
{
mem(Tree); mem(num); mem(index);
for(int i=;i<=N;i++)
{
scanf("%d", &num[i]);
Edit(, , N, i, );
index[i] = i;
}
sort(index+, index+N+,cmp);
long long ans = ;
for(int i=;i<=N;i++)
{
ans += (Search(, , N, , index[i])-);
Edit(, , N, index[i], -);
}
printf("%I64d\n", ans);
}
return ;
}
然后是归并排序(只有注释位置有改动,其他的无变化)4076K 438MS
#include <stdio.h>
#include <string.h>
#define mem(a) memset(a, 0, sizeof(a)) int N, A[], T[];
__int64 ans; void Merg_Sort(int x,int y)
{
if(y-x<=) return ;
int mid = x + (y-x)/;
Merg_Sort(x,mid);
Merg_Sort(mid,y);
int p = x, q = mid, i=x;
while(p<mid || q<y)
{
if(q>=y || (p<mid && A[p] <= A[q])) T[i++] = A[p++];
else//else的条件是(p==mid || A[q] < A[p])
{
if(p<mid) ans+=(mid-p);//由于是p<mid,所以此时也就是相当于 A[q] < A[p]
T[i++] = A[q++]; //上面同时A[p]是第一个<A[q]的数,所以+后面还有的数(mid-p)
}
}
for(i=x;i<y;i++)
{
A[i] = T[i];
}
} int main()
{
while(~scanf("%d", &N) && N)
{
mem(A); mem(T);
for(int i=;i<N;i++)
{
scanf("%d", &A[i]);
}
ans = ;
Merg_Sort(,N);
printf("%I64d\n",ans);//结果会超int32
}
return ;
}
POJ2299Ultra-QuickSort (线段树和归并排序的解法)的更多相关文章
- HDU 6318.Swaps and Inversions-求逆序对-线段树 or 归并排序 or 离散化+树状数组 (2018 Multi-University Training Contest 2 1010)
6318.Swaps and Inversions 这个题就是找逆序对,然后逆序对数*min(x,y)就可以了. 官方题解:注意到逆序对=交换相邻需要交换的次数,那么输出 逆序对个数 即可. 求逆序对 ...
- HDU1394 Minimum Inversion Number(线段树OR归并排序)
Minimum Inversion Number Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java ...
- 求逆序数的方法--线段树法&归并排序法
逆序数的概念:对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆 ...
- 主席树/线段树模拟归并排序+二分答案(好题)——hdu多校第4场08
用主席树写起来跑的快一点,而且也很傻比,二分答案,即二分那个半径就行 主席树求的是区间<=k的个数 #include<bits/stdc++.h> using namespace s ...
- [计蒜客T2238]礼物_线段树_归并排序_概率期望
礼物 题目大意: 数据范围: 题解: 这题有意思啊($md$卡常 直接做怎么做? 随便上个什么东西,维护一下矩阵乘和插入,比如说常数还算小的$KD-Tree$(反正我是没见人过过 我们漏掉了一个条件, ...
- POJ 1804 Brainman(5种解法,好题,【暴力】,【归并排序】,【线段树单点更新】,【树状数组】,【平衡树】)
Brainman Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10575 Accepted: 5489 Descrip ...
- POJ 2299 Ultra-QuickSort 逆序数 树状数组 归并排序 线段树
题目链接:http://poj.org/problem?id=2299 求逆序数的经典题,求逆序数可用树状数组,归并排序,线段树求解,本文给出树状数组,归并排序,线段树的解法. 归并排序: #incl ...
- 线段树菜鸟一题+归并排序【求逆序数】POJ2299
题目链接:http://poj.org/problem?id=2299 归并排序解法链接:http://blog.csdn.net/lyy289065406/article/details/66473 ...
- HDU 1394 Minimum Inversion Number(最小逆序数/暴力 线段树 树状数组 归并排序)
题目链接: 传送门 Minimum Inversion Number Time Limit: 1000MS Memory Limit: 32768 K Description The inve ...
随机推荐
- BZOJ 1724 切割木板
合并果子. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm& ...
- jquery ajax GET POST 跨域请求实现
同一段逻辑代码需要在多个网站中使用, 每个网站都新建一个ashx真是扯蛋的作法, 所以想只请求一处的ashx, 这样便于维护和修改, 那么,ajax跨域问题就来了. 废话少说, 直接上代码, 我现 ...
- C的结构体使用
C的结构体演示 #include <stdio.h> struct A //建立结构体A { char *name; int s1; struct A *next; }; void mai ...
- UVA 11796 - Dog Distance
题意 两条狗啊,同时跑,,同时结束,各自跑各自的道路,问跑的过程中,他们最大距离和最小距离的差: 方法 恶心一点就是,最大最小距离的求解方法,假设两只狗都只有一条线段要跑,则可以判定在端点处有最大 ...
- JVM——类的加载过程
附一张图方便理解,一个类的执行过程 类的加载过程,简明的来说 类装饰器就是寻找类的字节码文件并构造出类在JVM内部表示的对象组件.在Java中,类装载器把一个类装入JVM中,要经过以下步骤: 装载:查 ...
- 【转】Ubuntu 12.04 安装JDK 8和Eclipse
原文网址:http://blog.csdn.net/yechaodechuntian/article/details/24853813 Ubuntu 12.04 下安装 JDK8 方法一:(缺点是安装 ...
- 【转】Github轻松上手6-推荐follow的牛人和值得watch的repo
转自:http://blog.sina.com.cn/s/blog_4b55f6860100zzk5.html Github作为一个social coding 网站,其作用远远超过了一个简单的VCS( ...
- Android 一步步教你从ActionBar迁移到ToolBar
谷歌的材料设计也发布了有一段时间了,包括官方的support库 相信大家也熟悉了不少,今天就把actionbar 迁移到toolbar的 经验发出来. 这个地方要注意 我用的图标都是studio里的一 ...
- c排序算法大全
排序算法是一种基本并且常用的算法.由于实际工作中处理的数量巨大,所以排序算法 对算法本身的速度要求很高. 而一般我们所谓的算法的性能主要是指算法的复杂度,一般用O方法来表示.在后面将给出详细的说明.& ...
- 嵌入式 uboot引导kernel,kernel引导fs
1.uboot引导kernel: u-boot中有个bootm命令,它可以引导内存中的应用程序映像(Kernel),bootm命令对应 common/cmd_bootm.c中的do_bootm()函数 ...