分治算法
在提高组中应该考不了什么分值算法,咕咕咕,应该会考,比如二分,归并排序等知识点。
下面就对二分和分治排序做一个小总结。
一,何为分治算法
如字面意思,分而治之,分治算法是一种将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解,
这也是贪心,DP等的主要想法。
二,二分
二分的定义:将问题分解成两个较小的问题的方法叫做二分法。
二分的基本用途:在单调序列或单调函数中做查找操作,如果某个问题的答案具有单调性,那么我们就可以通过二分把对问题的求解变为对问题答案的判定
通过理论复杂度分析可知,对答案的二分判定的复杂度要小于对问题求解的复杂度。
三,二分法
在一个单调序列中,每次都将序列分为两部分,判断解在哪个部分并调整上下界,直到找到目标元素,每次二分以后都将舍弃一半的元素,因此二分效率很高。
二分法的实现
1,整数定义域上的二分
inline void divide(int l,int r) { l=1,r=n; while(l<=r) { int mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid; } return; }//伪代码
2,实数域上的二分查找(一般不用吧qwq,不太会)
四,二分法的常见模型
1,二分答案:最小值最大(或最大值最小)类的问题常常用二分解决,就是二分最值后配合其他算法检验其合理性,将最优值问题转化为判定性问题。
2,二分查找
3,代替三分
五,归并排序
归并排序分为两个步骤,分 和 和。
分:依次拆分区间为两个区间使每个区间有序,当拆分到每个区间只有一个元素时,每个区间就有序了。
和:将每两个有序的序列合并为一个有序的序列。
代码实现:
1:
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define maxn 100005 using namespace std; int num[maxn],n; inline void merge(int *num,int *a,int l,int r) { int i=l,j,k=l,mid=(l+r)>>1; for(i=l,j=mid+1;i<=mid&&j<=r;k++) { if(num[i]<=num[j]) a[k]=num[i++]; else a[k]=num[j++]; } while(i<=mid) a[k++]=num[i++]; while(j<=r) a[k++]=num[j++]; } inline void m_sort(int *num,int *a,int l,int r) { if(l==r) { a[l]=num[l]; } else { int t[maxn]; int mid=(l+r)>>1; m_sort(num,t,l,mid); m_sort(num,t,mid+1,r); merge(t,a,l,r); } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&num[i]); m_sort(num,num,1,n); for(int i=1;i<=n;i++) printf("%d ",num[i]); return 0; }
2:
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define maxn 100005 using namespace std; int num[maxn],n,a[maxn]; inline void m_sort(int l,int r) { if(l==r) return; int mid=(l+r)>>1; m_sort(l,mid); m_sort(mid+1,r); int p1=l,p2=mid+1; for(int i=l;i<=r;i++) { if(p1<=mid&&p2<=r) { if(num[p1]<num[p2]) a[i]=num[p1++]; else a[i]=num[p2++]; } else { if(p1<=mid) a[i]=num[p1++]; else a[i]=num[p2++]; } } for(int i=l;i<=r;i++) num[i]=a[i]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&num[i]); m_sort(1,n); for(int i=1;i<=n;i++) printf("%d ",num[i]); return 0; }
个人觉得第二种代码实现比较好理解,也比较容易与上面的理论结合。
六,归并排序求逆序对
1,逆序对:如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
我们考虑一下如何在合并的过程中求解逆序对,合并的过程是将两个有序的序列合并成一个序列,合并过程已经保证了序列a的编号小于序列b的编号,而我们知道
每个单独的序列是有序的,所以其中不可能存在逆序对,逆序对只可能存在于要合并的两个有序区间之间,所以我们只需要在合并的时候统计一下就好。
如果合并过程中,前一个序列中的某个数大于后一个序列的某个数,那么前面那个序列中从当前数开始到mid的所有数都和后一个序列中那个数构成逆序对。
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define maxn 500005 using namespace std; int num[maxn],n,a[maxn]; long long ans; inline void m_sort(int l,int r) { if(l==r) return; int mid=(l+r)>>1; m_sort(l,mid); m_sort(mid+1,r); int p1=l,p2=mid+1; for(int i=l;i<=r;i++) { if(p1<=mid&&p2<=r) { if(num[p1]<=num[p2]) a[i]=num[p1++]; else a[i]=num[p2++],ans+=(long long)mid-p1+1; } else { if(p1<=mid) a[i]=num[p1++]; else a[i]=num[p2++]; } } for(int i=l;i<=r;i++) num[i]=a[i]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&num[i]); m_sort(1,n); printf("%lld",ans); return 0; }
最后祝各位OIer NOIP 2019 RP++ SCORE++