分治与归并(数列分治)

数列分治

通过两个题目了解吧,是求逆序数的,就是运用分治与归的思想,我自己的理解是:通过把该问题分解为好多的子问题,然后一次次胡归并得到答案。

题目链接:https://cn.vjudge.net/contest/243680#problem/C

题意

两道题目的意思是一致的,都是数列分治的模板题目。

题解

下面附两个代码,一个是该题的模板code和详细的解释(注释)。另一个是学长的关于数列分治的演示代码。

                                                                                                                                                                                                             

Code One

#include <iostream>  //数列分治(求逆序对数目)。方法:分治与归并。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <functional>
#include <algorithm>
#define _USE_MATH_DEFINES
using namespace std;
typedef long long ll;
const int MAXN = 200000+5;
int a[MAXN], b[MAXN]; //a用于存储数据,b用于调整当前区间中的元素位置,使之按从大到小的顺序排列
ll sum = 0;//存储结果
void guibing(int l, int mid, int r) //归并函数:对分治(二分)后的区间进行规并,就是对二分后的区间再次合并起来,计算当前合并后区间的逆序对数。
{
    int i = l, j = mid+1, k = 0;
    while(i<=mid && j<=r) //进行遍历,同时计算逆序对数,并同时对该区间的元素位置顺序按从大到小保存在b数组内。
    {//(a[i]->a[mid] 和 a[mid+1]->a[r] 分别都是已经从大到小排序过的)
        if(a[i]<=a[j])//当a[i] <= a[j] ,说明目前左半边区间最大的小于右半边区间最大的,则应将a[j]放入b数组中。
        {
            b[++k] = a[j++]; //交换顺序,使大的在前,小的在后
        }
        else //如果a[i]>[j],说明a[i]大于目前右半边区间最大的,则应将a[i]放入b数组内,同时通过运用数组下标计算出a[i]组成的逆序对数。
        {
            b[++k] = a[i++];
            sum = sum + r - j + 1; //因为顺序是自己按照从大到小的顺序排列的,所以遇见(a[i] > a[j])时才可知道逆序数对的出现。
        }
//        cout<< sum <<endl;
    }
    while(i<=mid) //若i仍小于mid,则说明左半边的数字较小,应将左半边剩下的元素依次放入到b数组内
    {
        b[++k] = a[i++];
    }
    while(j<=r)//若j仍小于mid,则说明右半边的数字较小,应将右半边剩下的元素依次放入到b数组内
    {
        b[++k] = a[j++];
    }
    int w = l;
    for(int i=1; i<=k; i++) //按b中排好的顺序赋值于a中相对应的位置。(将a[i]->a[j]整体从大到小排序,以用于下一次的归并)
    {
        a[w++] = b[i];
    }
}
void fenzhi(int l, int r)
{
    if(l==r) return ;//递归结束条件(随后返回到递归入口),当l==r时,已经递归到了最底层。
    int mid = (l+r)>>1;//进行分治操作,(二分递归)。
    fenzhi(l, mid);
    fenzhi(mid+1, r);
    guibing(l,mid,r);//对分治后的,进行一步步的归并。
}

int main()
{
    int n;
    cin>> n;
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
    }
    fenzhi(1,n);
//    for(int i=1; i<=n; i++)
//    {
//        printf("%d ",a[i]);
//    }
//    printf("\n");
    cout<< sum << endl;


    return 0;
}

Code Two(数列分治演示)

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5+5;

int a[N], ans[N];

ll solve(int l, int r)
{
	cout << "l = " << l << " " << "r = " << r <<endl; 
    //划分并解决子问题
    int mid = (l+r)>>1;
    cout << "mid = " << mid << endl;
    if(l==r)    
	{
		cout << "return" << endl;
		return 0;
	}
    ll num = 0;//逆序对的个数

    num += solve(l, mid);
    num += solve(mid+1, r);

    //合并子问题

    //每一次的处理结果,升序保存在ans数组
    //将属于不同子序列的逆序对个数累加
    for(int i = l, j = mid+1, k = 0; i<=mid||j<=r; k ++)
    {
        if(i>mid)   ans[k] = a[j++];
        else if(j>r)    ans[k] = a[i++];
        else if(a[i]<=a[j]) ans[k] = a[i++];
        else
        {
            //出现逆序对
            ans[k] = a[j++];
            num += mid-i+1;//B序列中大于a[j]的个数
        }
    }
    for(int i = 0; i <= (r-l); i ++)
        a[l+i] = ans[i];
    for(int i = 0; i <= r; i++)
        printf("%d ", a[i]);
    puts("");
    cout << "num = " << num << " " << "return" << endl;
    return num;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        scanf("%d", a+i);
    printf("%lld\n",solve(0, n-1));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Alibaba_lhl/article/details/81393355
今日推荐