数列分治
通过两个题目了解吧,是求逆序数的,就是运用分治与归的思想,我自己的理解是:通过把该问题分解为好多的子问题,然后一次次胡归并得到答案。
题目链接: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;
}