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=mid−lowbit(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
k−1个删除的元素与第
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[k−1]−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(x−lowbit(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] [x−lowbit(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)] [x−lowbit(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;
}