蒟蒻的ACM算法(七)-CDQ分治

不得不说

  本来标题想写分治,但是想了想发现自己分治能说的不多,主要的内容就是 C D Q CDQ CDQ分治.便取了这个标题.

预备知识

  • 关于什么是分治
      分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。在计算机科学中,分治法就是运用分治思想的一种很重要的算法。分治法是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)等等。
  • 一般步骤
  1. 划分步:把输入的问题划分为k个子问题,并尽量使这 k k k个子问题的规模大致相同。
  2. 治理步:当问题的规模大于某个预定的阈值 n 0 n_0 n0时,治理步由 k k k个递归调用组成。
  3. 组合步:组合步把各个子问题的解组合起来,它对分治算法的实际性能至关重要,算法的有效性很大地依赖于组合步的实现。
  • 时间复杂度
    • 直观估计
      • 分治由以上三部分构成,整体时间复杂度则由这三部分的时间复杂度之和构成.
      • 由于递归,最终的子问题变得极为简单,以至于其时间复杂度在整个分治策略上的比重微乎其微.
  • 经典例题
    • 归并排序,快排等
    • 求逆序对等

经典例题

A t c o d e r A   −   C o l o r f u l   S u b s e q u e n c e Atcoder\\ A\ -\ Colorful\ Subsequence AtcoderA  Colorful Subsequence
https://atcoder.jp/contests/agc031/tasks/agc031_a

  • 题目简析:
    • 问多少种子序列,子序列中的字母不同.
    • 列如 b a a baa baa,包括: b , a , a , b,a,a, b,a,a,两个不同位置 a a a b a ba ba,总计 5 5 5个, b a a baa baa排除是因为 a a a是重复的.
  • 解法
    • 先将每个字母的个数统计下来,然后分治计算,一个字母的时候,答案是该字母出现的次数.
    • 只有两个字母的时候,如 a b ab ab,包含的排列有 a , b , a b a,b,ab a,b,ab,相当于 ′ a 的 个 数 , b 的 个 数 , a 和 b 组 合 个 数 ′ 的 加 和 'a的个数,b的个数,a和b组合个数'的加和 a,b,ab,而 a 和 b 组 合 个 数 , 则 是 a 的 个 数 × b 的 个 数 a和b组合个数,则是a的个数\times b的个数 ab,a×b
    • 同理可得, a n s ans ans即为 a n s L + a n s R + a n s L × a n s R ansL+ansR+ansL\times ansR ansL+ansR+ansL×ansR.
#include<iostream>
#include<algorithm>
#include<string>
#include<queue>
#include<stdio.h>
#include<stdlib.h>
#ifndef null
#define null -1
#endif
using namespace std;

const int MAXN = 2e5 + 10;
const int INF = 1e5 + 10;
const int MOD = 1e9 + 7;
typedef long long ll;

char s[INF];
int num[27];
string s1="0";
ll solve(int L, int R)
{
	if (L == R)
		return num[s1[L] - 'a'];
	int mid = (L + R) >> 1;
	ll nL = solve(L, mid), nR = solve(mid + 1, R);
	return (nL%MOD + nR%MOD + (nL%MOD * nR%MOD)%MOD)%MOD;
}
int main()
{
	int n;
	cin >> n >> s;
	for (int i = 0; i < n; i++) {
		if (!num[s[i] - 'a'])
			s1 = s1 + s[i];
		num[s[i] - 'a']++;
	}

	cout << solve(1, s1.length() - 1)<<endl;

	return 0;
}

CDQ分治

前面絮絮叨叨的简单介绍了下分治,想必各位对分治有了一定认识.
下面是重头戏: C D Q CDQ CDQ分治.
这个算法,是由陈丹琦大牛在论文中提出的%%%.
首先,我们需要知道一些事情:

  • 优势在于可以顶替复杂的高级数据结构,而且常数比较小
  • 缺点在于必须离线操作

用来解决什么问题呢?

  • 首先,分治问题2333
  • 分治后的答案,不仅单单考虑子问题 { L , m i d } \{L,mid\} {L,mid}和子问题 { m i d + 1 , R } \{mid+1,R\} {mid+1,R}.
  • 还需要考虑子问题 { L , m i d } \{L,mid\} {L,mid}对子问题 { m i d + 1 , R } \{mid+1,R\} {mid+1,R}的影响 / / /联系产生的答案.

列如:

  • 二维偏序问题
    • 给定一个二元组 { x , y } \{x,y\} {x,y},要求问有多少对 { x i , y i } , { x j , y j } \{x_i,y_i\},\{x_j,y_j\} {xi,yi},{xj,yj}满足 x i &gt; x j &amp; &amp; y i &gt; y j x_i&gt;x_j\&amp;\&amp;y_i&gt;y_j xi>xj&&yi>yj
    • 解法为:
      • 先将二元组按照 x x x的大小排列.
      • 分治后,我们分别知道 { L , m i d } \{L,mid\} {L,mid}区间和 { m i d + 1 , R } \{mid+1,R\} {mid+1,R}区间内的解
      • 再计算跨过 m i d mid mid的两对点,对 { L , m i d } \{L,mid\} {L,mid} { m i d + 1 , R } \{mid+1,R\} {mid+1,R}中的二元组按照 y y y的大小排序
      • 由于先前分组便已经对 x x x进行排序,所以,只需要二分便可以求得左区间相对于右区间的点的个数.
  • 三维偏序问题
    • 和二维偏序问题类似,但有一定不同
    • 给定一个三元组 { x , y , z } \{x,y,z\} {x,y,z},要求问有多少对 { x i , y i , z i } , { x j , y j , z j } \{x_i,y_i,z_i\},\{x_j,y_j,z_j\} {xi,yi,zi},{xj,yj,zj}满足 x i &gt; x j &amp; &amp; y i &gt; y j &amp; &amp; z i &gt; z j x_i&gt;x_j\&amp;\&amp;y_i&gt;y_j\&amp;\&amp;z_i&gt;z_j xi>xj&&yi>yj&&zi>zj
    • 解法为:
      • 先将三元组按照 x x x的大小排列.
      • 分治后,我们分别知道 { L , m i d } \{L,mid\} {L,mid}区间和 { m i d + 1 , R } \{mid+1,R\} {mid+1,R}区间内的解
      • 再计算跨过 m i d mid mid的两对点,对 { L , m i d } \{L,mid\} {L,mid} { m i d + 1 , R } \{mid+1,R\} {mid+1,R}中的三元组按照 y y y的大小排序
      • 由于先前分组便已经对 x x x进行排序,所以,只需要二分便可以求得满足 y y y条件的点.
      • 再建立一个权值树状数组 / / /线段树,再将上面符合的 x , y , z {x,y,z} x,y,z对应中满足不等式的 z z z的点求出.

例题


伪代码

void cdq(int left,int right)
{
	if(left==right)
		return ;
	int mid=(left+right)>>1;
	cdq(left,mid),cdq(mid+1,right);
	sort(a+l,a+mid+1,cmp);
	sort(a+mid+1,a+right+1,cmp);
	
	/**
	*处理左区间对于右区间影响的代码
	*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值