Live2D

分治算法

分治算法

在提高组中应该考不了什么分值算法,咕咕咕,应该会考,比如二分,归并排序等知识点。

下面就对二分和分治排序做一个小总结。

 


一,何为分治算法

如字面意思,分而治之,分治算法是一种将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解,

这也是贪心,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;
}
View Code

 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;
} 
View Code

个人觉得第二种代码实现比较好理解,也比较容易与上面的理论结合。

六,归并排序求逆序对

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;
} 
View Code

 

 

 


最后祝各位OIer NOIP 2019 RP++ SCORE++

 

posted @ 2019-08-14 14:41  Hoyoak  阅读(618)  评论(0编辑  收藏  举报