对CDQ分治的一些理解

CDQ分治与树状数组(BZOJ3295)

之前有简单接触过CDQ分治,后来讨论说CDQ多数可以写成非递归形式,在学弟的建议下就写一个博文把。
这个东西其实和树状数组遍历方式非常相似。
我对CDQ的理解可能比较浅显。所以我对CDQ的理解只是以下面贡献的形式来理解。
CDQ分治通过将问题分割成两种贡献:
1:段内贡献
2:段间贡献
比如说问题规模为 n n n,初始问题为 C D Q ( 1 , n ) CDQ(1,n) CDQ(1,n)
那么CDQ分治将问题分为 C D Q ( l , m i d ) CDQ(l,mid) CDQ(l,mid) C D Q ( m i d + 1 , r ) CDQ(mid+1,r) CDQ(mid+1,r)
在递归进入子问题时,可以理解为计算第一种贡献,也就是 :段内贡献。
通常情况下,贡献如果时从左往右传递时,则:
CDQ分治先计算左节点,即 C D Q ( 1 , m i d ) CDQ(1,mid) CDQ(1,mid)
之后计算段间贡献 O ( 且 较 少 时 间 合 并 ) O(且较少时间合并) O()
最后计算 右节点,即 C D Q ( m i d + 1 , n ) CDQ(mid+1,n) CDQ(mid+1,n)
这样计算的复杂度就是 : O ( 较 少 时 间 ∗ l o g n ) O(较少时间*logn) O(logn)
显然,CDQ这种方式计算在二叉树上是中顺的。
而树状数组也是中顺的。 ( 强 烈 建 议 将 遍 历 顺 序 与 二 进 制 结 合 , 看 一 下 规 律 ) (强烈建议将遍历顺序与二进制结合,看一下规律) (
那么我们固定 m i d mid mid,而通过 m i d mid mid选择 l l l r r r来实现一个非递归的 C D Q CDQ CDQ
l = m i d − l o w b i t ( m i d ) + 1 l=mid-lowbit(mid)+1 l=midlowbit(mid)+1 , r = m i d + l o w b i t ( m i d ) r = mid+lowbit(mid) r=mid+lowbit(mid)
l o w b i t ( x ) lowbit(x) lowbit(x) x x x二进制形式从底位到高位第一个1代表的数值大小
int lowbit(x)
{
	return x&(-x);
}
非递归版本的 c d q cdq cdq分治可以为如下代码结构:
for(int x=1;x<=n;x++)
{
	int s= lowbit(x);
	int begin = x-s+1, end = x + s + 1;
	//简单处理
	for(int j=begin; j < end;j++)
	{
		计算[begin,x][x+1,end]的贡献
	}
}
以这种方式计算的贡献不重不漏

以BZOJ3295为例:

https://www.lydsy.com/JudgeOnline/problem.php?id=3295

注意:题目删除的是元素,而不是位置。
a n s [ k ] ans[k] ans[k]为第 k k k次询问逆序数量。
c o u n t [ k ] count[k] count[k]为原序列第 k k k个元素所参与组成的逆序对个数
Q [ k ] Q[k] Q[k]为第 k k k次删除的元素的初始位置
f [ k ] f[k] f[k]为前 k − 1 k-1 k1个删除的元素与第 Q [ k ] Q[k] Q[k]个元素组成的逆序对个数
那么有: a n s [ k ] = a n s [ k − 1 ] − c o u n t [ Q [ k ] ] + f [ k ] ans[k]=ans[k-1]-count[Q[k]]+f[k] ans[k]=ans[k1]count[Q[k]]+f[k]
这里,加上 f f f作为贡献补充是显而易见的。
那么快速计算 f f f是问题的关键
使用 C D Q CDQ CDQ分治,来计算 f f f

A ( x ) A(x) A(x) C D Q ( x − l o w b i t ( x ) + 1 , x ) CDQ(x-lowbit(x)+1,x) CDQ(xlowbit(x)+1,x)
B ( x ) B(x) B(x) C D Q ( x + 1 , x + l o w b i t ( x ) ) CDQ(x+1,x+lowbit(x)) CDQ(x+1,x+lowbit(x))
这里 A ( x ) A(x) A(x) f f f [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [xlowbit(x)+1,x]上的段内贡献
这里 B ( x ) B(x) B(x) f f f [ x , x + l o w b i t ( x ) ] [x,x+lowbit(x)] [x,x+lowbit(x)]上的段内贡献
注意,当循环进行到A(x)时,其实已经计算完成了 [ 1 , x ] [1,x] [1,x]上的 f f f
那么 [ x + 1 , n ] [x+1,n] [x+1,n]可以认为是未删除的元素。
那么 A ( x ) A(x) A(x) B ( x ) B(x) B(x)的贡献段间贡献就是先对 [ x − l o w b i t ( x ) + 1 , x + l o w b i t ( x ) ] [x-lowbit(x)+1,x+lowbit(x)] [xlowbit(x)+1,x+lowbit(x)]删除元素按照位置排序。
排序后,统计对于每一个在 B ( x ) B(x) B(x)中的元素:
A ( x ) A(x) A(x)中已经删除的元素位置下标小于它,但数值大于它
或者位置坐标大于他,但数值小于它的元素个数。
这个数就是A(x)对B(x)的贡献。

代码如下:

#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#define MAXN 100005
#define MAXM 50005
using namespace std;

int lowbit(int x)
{
	return x&(-x);
}

struct Array    //树状数组
{
	int n;
	int D[MAXN];
	int vis[MAXN];
	int flag;
	Array()
	{
		flag = 1;
		memset(D, 0, sizeof D);
		memset(vis, 0, sizeof vis);
	}
	void clear()
	{
		flag++;
	}
	void add(int a, int on)
	{
		while (a&&a <= n)
		{
			if (vis[a] < flag)
			{
				vis[a] = flag;
				D[a] = 0;
			}
			D[a]++;
			a += on*lowbit(a);
		}
	}
	int sum(int a, int on)
	{
		int tmp = 0;
		while (a&&a <= n)
		{
			if (vis[a] == flag) tmp += D[a];
			a += on*lowbit(a);
		}
		return tmp;
	}
};
Array A;

int id[MAXN];
int Q[MAXN];
int Da[MAXN];
int f[MAXM];
int Count[MAXN];
int Rank[MAXN];

bool cmp(const int &a, const int &b)
{
	return Q[a] < Q[b];
}

int main()
{
	long long total = 0;
	int n, m;
	scanf("%d %d", &n, &m);
	A.n = n;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", Da + i);
		Rank[Da[i]] = i;
	}
	for (int i = 1; i <= m; i++)
	{
		scanf("%d", Q + i);
		Q[i] = Rank[Q[i]];
	}
	A.clear();
	for (int i = 1; i <= n; i++)
	{
		Count[i] += A.sum(Da[i], 1);
		A.add(Da[i], -1);
		total += Count[i];
	}
	A.clear();
	for (int i = n; i; i--)
	{
		Count[i] += A.sum(Da[i], -1);
		A.add(Da[i], 1);
	}
	for (int i = 1; i <= m; i++)
	{
		int s = lowbit(i);
		int begin = i - s + 1, end = i + s + 1;
		if (end > m)end = m + 1;
		printf("%lld\n", total);
		total += f[i] - Count[Q[i]];
		for (int k = begin; k < end; k++)id[k] = k;
		sort(id + begin, id + end, cmp);
		A.clear();
		for (int k = begin; k < end; k++)
		{
			int j = id[k];
			if (j > i)
				f[j] += A.sum(Da[Q[j]], 1);
			else
				A.add(Da[Q[j]], -1);
		}
		A.clear();
		for (int k = end - 1; k >= begin; k--)
		{
			int j = id[k];
			if (j > i)
				f[j] += A.sum(Da[Q[j]], -1);
			else
				A.add(Da[Q[j]], 1);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值