对于『CDQ分治』的简单理解

关于CDQ分治

归并排序求逆序对的主要思想就是将一个序列分成两半,保证两边数值的单调性;然后用左边的数值去更新右边的数值。而 C D Q CDQ CDQ分治同样是这样,将某一个序列分成两半,然后用左边的区间更新右边的区间,最终得到了答案。

事实上,我们对求逆序对的问题做一个转化:对于每一个数都可以看做是一份数对 ( x , y ) , x (x,y),x (x,y),x表示所处的数列中的位置,显然初始的都是 1 , 2 , 3 , . . . , n , 1,2,3,...,n, 1,2,3,...,n, y y y表示具体数值。那个每一个逆序对都可以表示为: x 1 < x 2 x_1<x_2 x1<x2 y 1 > y 2 . y_1>y_2. y1>y2.

求二维偏序问题

二维偏序问题,其实就是逆序对或者顺序对问题。或者定义的数对比较问题。我们如果题目中只给了一个数列,第一维是位置。其基本流程如下:

  • 对于求解区间 [ l , r ] [l,r] [l,r]的值,先分治为 [ l , m i d ] , [ m i d + 1 , r ] [l,mid],[mid+1,r] [l,mid],[mid+1,r],其中 m i d = l + r 2 . mid=\frac{l+r}{2}. mid=2l+r.
  • 比较当前数值,若取左边区间的数,统计某一个变量的值,例如累加一个 s u m sum sum
  • 若取的是右边的数,累加先前统计的值,例如 s u m sum sum
  • 归并排序,时区间 [ l , r ] [l,r] [l,r]的第二维序列具有单调性。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

这份代码以求顺序对为例:

#include 

using namespace std;

inline int read(void)
{
	int s = 0;char c = getchar();
	while (c<'0' || c>'9') c = getchar();
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s;
}

struct node 
{
	int x,y,num,ans;
	friend bool operator <= (node p1,node p2) 
	{
		return p1.y <= p2.y;
	} 
} ;

int n;
node t[500000];
node a[500000];

void cdq(int l,int r)
{
	int mid = l+r >> 1;
	int h1 = l, h2 = mid+1, p = l-1;
	if (l >= r) return;
	cdq(l,mid);
	cdq(mid+1,r);
	while (h1 <= mid && h2 <= r)
	{
		if (a[h1] <= a[h2]) t[++p] = a[h1++];
		else
		{
			a[h2].ans += h1-l;
			t[++p] = a[h2++];
		}
	}
	while (h1 <= mid) t[++p] = a[h1++];
	while (h2 <= r) a[h2].ans += mid-l+1, t[++p] = a[h2++];
	for (int i=l;i<=r;++i) a[i] = t[i];
	return; 
}

bool cmp(node p1,node p2)
{
	if (p1.x == p2.x) return p1.y < p2.y;
	return p1.x < p2.x;
}

bool compare(node p1,node p2)
{
	return p1.num < p2.num;
}

int main(void)
{
	freopen("2026.in","r",stdin);
	freopen("2026.out","w",stdout);
	n = read();
	for (int i=1;i<=n;++i) 
	{
		a[i].x = read();
		a[i].y = read();
		a[i].num = i;
	    a[i].ans = 0;
	}
	sort(a+1,a+n+1,cmp);
	cdq(1,n);
	sort(a+1,a+n+1,compare);
	for (int i=1;i<=n;++i) printf("%d\n", a[i].ans);
	return 0;
}

单点修改·区间求和问题

我们设数对为(修改时间,修改数值)。由于不同的操作,我们使其赋予不同的类型。对于区间查询,可以得到区间1-右端点的数值,再得到1-左端点的数值,两者相减即可。

我们对于这一些操作进行 C D Q CDQ CDQ分治:

  • 分成左右两半进行分治操作。
  • 将序列左右队头进行比较:若左边较小,且类型为修改,累加到 s u m sum sum里。若右边较小,且标记为右端点,加上 s u m sum sum,如果是左端点,就减去 s u m sum sum
  • 将两个序列合并。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

代码如下:

#include 

using namespace std;

struct node
{
	int type, index, val;
	friend bool operator < (node p1,node p2)
	{
		if (p1.index == p2.index) return p1.type<p2.type;
		return p1.index<p2.index;
	}
} ;

int tot,cnt,n,m;
int ans[500000];
node t[500000];
node query[500000];

inline int read(void)
{
	int s = 0;char c = getchar();
	while (c<'0' || c>'9') c = getchar();
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s;
}

void add(int a,int b,int c)
{
	tot ++;
	query[tot].type = a;
	query[tot].index = b;
	query[tot].val = c;
	return;
}

void CDQ(int l,int r)
{
	if (l >= r) return;
	int mid = l+r >> 1;
	CDQ(l,mid), CDQ(mid+1,r);
	int h1 = l,h2 = mid+1,p = l-1, sum = 0;
	while (h1 <= mid && h2 <= r)
	{
		if (query[h1] < query[h2])
		{
			if (query[h1].type == 1) sum += query[h1].val;
			t[++p] = query[h1++];
		}
		else
		{
			if (query[h2].type == 2) ans[query[h2].val] -= sum;
			if (query[h2].type == 3) ans[query[h2].val] += sum;
			t[++p] = query[h2++];
		}
	}
	while (h1 <= mid) t[++p] = query[h1++];
	while (h2 <= r) 
	{
		if (query[h2].type == 2) ans[query[h2].val] -= sum;
		if (query[h2].type == 3) ans[query[h2].val] += sum;
		t[++p] = query[h2++]; 
	}
	for (int i=l;i<=r;++i) query[i] = t[i];
	return;
} 

int main(void)
{
	freopen("shulie.in","r",stdin);
	freopen("shulie.out","w",stdout);
	tot = cnt = 0;
	n = read();
	for (int i=1;i<=n;++i) 
	{
		int x = read();
		add(1,i,x);
	}
	m = read();
	for (int i=1;i<=m;++i)
	{
		int type;
		char c = getchar();
		while (c != 'S' && c != 'A') c = getchar();
		if (c == 'S') type = 2;
		if (c == 'A') type = 1;
		int a = read(), b = read();
		if (type == 1) add(type,a,b);
		else add(2,a-1,++cnt),add(3,b,cnt);
		//2表示左端点,3表示右端点 
	}
	CDQ(1,tot);
	for (int i=1;i<=cnt;++i) printf("%d%c",ans[i],i==cnt?'\n':'\n');
	return 0;
}

三维偏序问题

方法1:
C D Q CDQ CDQ分治+树状数组。先将第一维排序,然后用 c d q cdq cdq分治处理出第二维,因为需要求出第三维的逆序对,每一次比较的时候如果取左边区间则把这个数放进树状数组里;如果是右边的数则累加树状数组内的答案。

时间复杂度: O ( N   l o g N   l o g V ) O(N\ logN\ logV) O(N logN logV)

方法2:
两遍 C D Q CDQ CDQ分治。同样将第一维进行排序,用 c d q cdq cdq分治处理出第二维,并标记是所属的左区间还是所属的右区间。在当前合并完以后,即在结束这一个函数直接,再调用第二个 c d q cdq cdq函数。这个函数是用来统计第三维的答案,如果取的是做区间且在第二维标记的做区间,和累加数值;若取得是右区间且在第二维中标记的也是右区间,则累加答案。

时间复杂度: O ( n   l o g 2 n ) O(n\ log^2n) O(n log2n)

代码如下:

#include 

using namespace std;

int n,r;
struct node
{
	int a,b,c,pos,num;
	friend bool operator < (node p1,node p2)
	{
		if (p1.a ^ p2.a) return p1.a<p2.a;
        if (p1.b ^ p2.b) return p1.b<p2.b;
        return p1.c<p2.c;
	}
	friend bool operator == (node p1,node p2) {
		return p1.a == p2.a && p1.b == p2.b && p1.c == p2.c;
	}
} ;
int CNT[500000];
int ans[500000];
node a[500000];
node t[500000];
node T[500000];

inline int read(void)
{
	int s = 0;char c = getchar();
	while (c<'0' || c>'9') c = getchar();
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s;
}

void CDQ(int l,int r)
{
	int mid = l+r >> 1;
	if (l >= r) return ;
	CDQ(l,mid);
	CDQ(mid+1,r);
	int h1 = l, h2 = mid+1, k = l-1, sum = 0;
	while (h1 <= mid && h2 <= r)
	{
		if (t[h1].c <= t[h2].c)
		{
			if (t[h1].pos == 0) sum ++;
			//如果是左区间则答案合法 
			T[++k] = t[h1++];
		}
		else
		{
			if (t[h2].pos == 1) ans[t[h2].num] += sum;
			//如果是右区间则统计答案 
			T[++k] = t[h2++];
		}
	}
	while (h1 <= mid) T[++k] = t[h1++];
	while (h2 <= r) 
	{
		if (t[h2].pos == 1) ans[t[h2].num] += sum;
		T[++k] = t[h2++]; 
	}
	for (int i=l;i<=r;++i) t[i] = T[i];
	return;
}

void Cdq(int l,int r)
{
	int mid = l+r >> 1;
	if (l >= r) return ;
	Cdq(l,mid);
	Cdq(mid+1,r);
	int h1 = l, h2 = mid+1, k = l-1;
	while (h1 <= mid && h2 <= r)
	{
		if (a[h1].b <= a[h2].b) a[h1].pos = 0, t[++k] = a[h1++];
		//在相等的情况下让第一维大的先进 
		else a[h2].pos = 1, t[++k] = a[h2++];
		//pos记录左右区间,保证第一维的正确性 
	}
	while (h1 <= mid) a[h1].pos = 0, t[++k] = a[h1++];
	while (h2 <=  r ) a[h2].pos = 1, t[++k] = a[h2++];
	for (int i=l;i<=r;++i) a[i] = t[i];
	CDQ(l,r);
	return;
}

int main(void)
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	n = read();
	r = read();
	for (int i=1;i<=n;++i)
	{
		a[i].a = read();
		a[i].b = read();
		a[i].c = read();
		a[i].num = i;
	}
	sort(a+1, a+n+1);
	for (int i=n;i;--i)
	    if (a[i] == a[i+1])
	        ans[a[i].num] = ans[a[i+1].num]+1;
	Cdq(1,n);
	for (int i=1;i<=n;++i) CNT[ans[i]] ++;
	for (int i=0;i<n;++i)
	    printf("%d\n", CNT[i]);
	return 0; 
} 

二维求和问题

题目相当为二维树状数组的问题。

做法是:三维偏序问题,标记通以为求和问题。

代码如下:

#include 

using namespace std;

int n,m;

inline int read(void)
{
	int s = 0;char c = getchar();
	while (c<'0' || c>'9') c = getchar();
	while (c>='0' && c<='9') s = s*10+c-48,c = getchar();
	return s;
}

struct node
{
	int type,x,y,val;
	friend bool operator <= (node p1,node p2)
	{
		if (p1.x ^ p2.x) return p1.x<p2.x;
		return p1.type<p2.type;
	}
} ;
int ans[5000000];
node a[5000000];
node t[5000000];

struct Tree
{
	int S[20010000];
	#define lowbit(i) (i&-i)
	void add(int x,int v)
	{
		for (int i=x;i<=n;i+=lowbit(i)) 
			S[i] += v;
		return;
	}
	int ask(int x)
	{
		int sum = 0;
		for (int i=x;i;i-=lowbit(i))
		    sum += S[i];
		return sum;
	}
} ;

Tree tree;

void add(int A,int c,int d,int e)
{
	m ++;
	a[m].type = A;
	a[m].x = c;
	a[m].y = d;
	a[m].val = e;
	return;
}

void cdq(int l,int r)
{
	int mid = l+r >> 1;
	if (l >= r) return ;
	cdq(l, mid);
	cdq(mid+1, r);
	int h1 = l, h2 = mid+1, k = l-1;
	while (h1 <= mid && h2 <= r)
	{
		if (a[h1] <= a[h2]) 
		{
			if (a[h1].type == 1) tree.add(a[h1].y,a[h1].val);
			t[++k] = a[h1++];
		}
		else
		{
			if (a[h2].type == 2) ans[a[h2].val] -= tree.ask(a[h2].y);
			if (a[h2].type == 3) ans[a[h2].val] += tree.ask(a[h2].y);
			t[++k] = a[h2++];
		}
	}
	while (h1 <= mid) 
	{
		if (a[h1].type == 1) tree.add(a[h1].y,a[h1].val);
		t[++k] = a[h1++];
	}
	while (h2 <= r) 
	{
		if (a[h2].type == 2) ans[a[h2].val] -= tree.ask(a[h2].y);
		if (a[h2].type == 3) ans[a[h2].val] += tree.ask(a[h2].y);
		t[++k] = a[h2++];
	}
	for (int i=l;i<=mid;++i) 
		if (a[i].type == 1) tree.add(a[i].y,-a[i].val);
	//将加上的减回 
	for (int i=l;i<=r;++i) a[i] = t[i];
	return;
}

int main(void)
{
	freopen("mokia.in","r",stdin);
	freopen("mokia.out","w",stdout);
	int tot = 0, cnt = 0;
	n = read();
	while (true)
	{
		int type = read();
		if (type == 3) break;
		tot ++;
		int x = read();
		int y = read();
		if (type == 1) 
		{
			int v = read();
			add(type, x, y, v); 
		}
		if (type == 2)
		{
			int xx = read();
			int yy = read();
			xx ^= x ^= xx ^= x;
			yy ^= y ^= yy ^= y;
			add(3, x, y, ++cnt);
			add(2, xx-1, y, cnt);
			add(2, x, yy-1, cnt);
			add(3, xx-1, yy-1, cnt);
			//2表示减法 3表示加法 
		}
	}
	cdq(1, m);
	for (int i=1;i<=cnt;++i) printf("%d\n",ans[i]);
	return 0;
} 

你可能感兴趣的:(分治)