首页 > 其他分享 >CDQ分治

CDQ分治

时间:2023-03-22 10:11:06浏览次数:31  
标签:node le int 分治 mid solve CDQ

这是一个比较人类智慧的算法,尽管它大多数时候都不是出题人想要考察的算法,但是绝大部分时候出题人都没办法卡掉你然后愤然强制在线。

在怎样的情况下才能使用 cdq 分治?一般有如下情况:

  • 解决点对问题 \((i,j)\)。

    在算点对贡献时,我们将贡献拆成三类

    • \(i\in[1,mid],j\in[1,mid]\)
    • \(i\in[1,mid],j\in[mid + 1, n]\)
    • \(i\in[mid + 1,n],j\in[mid + 1, n]\)

    此时我们发现可以将整个序列拆成两段,\((1,mid)\) 和 \((mid + 1, n)\)。我们可以递归地处理第一种和第三种点对,我们只要想办法合并信息处理第二类点对即可。

  • 动态规划的优化和转移

    这类题目一般是一维的 dp 式,时间复杂度达到了 \(n^2\) 级别,而我们有时可以用 cdq 分治做到 \(\log n\) 转移。

    下文我们将提到这种应用。

  • 把动态问题转换成静态问题

    这是老套路了,有的题目会有修改操作和查询操作,我们可以离线询问,对时间序列分治,下文也会给出详细解释。

点对问题

经典问题,对每个 \(d\in[0,n)\) 求 满足 \(a_j\le a_i,b_j\le b_i,c_j\le c_i,i\not=j\) 的 \(j\) 的个数有 \(d\) 个 的 \(i\) 的个数。我们发现这是一个三维偏序,先考虑按某一维排序消去一个影响。

solve(l, r) 中,假设我们已经解决了 solve(l, mid)solve(mid + 1, r),问题来到了如何求 \(l \le j \le mid,mid + 1\le i\le r\) 这种点对上。由于排序过,左侧点不再对右侧点做出贡献。因此只要算出有多少满足 \(b_j \le b_i,c_j\le c_i\) 的 \(j\) 的数量。

先将区间内部的点排序,那么对于每个 \(i\),对应的可能的答案是一段前缀,接下来就是如何统计在这个前缀中满足 \(c_j \le c_i\) 的数量,这里显然使用树状数组。

以下是代码,注意一些精细化实现

const int N = 1e5 + 5;
int n, k, cnt, f[N];
struct node {
    int a, b, c, val, ans;
    node() {}
    node(int _a, int _b, int _c) : a(_a), b(_b), c(_c) {}
    il bool operator!=(const node& _eps) {return a ^ _eps.a || b ^ _eps.b || c ^ _eps.c; }
}o[N], lsh[N];
struct BIT {
#define lowbit(x) (x & (-x))
    int tr[N << 1], n;
    il void resize(const unsigned& lim) { n = lim; }
    il void add(int p, int v) { for(; p <= n; p += lowbit(p)) tr[p] += v; }
    il int query(int p) { int sum = 0; for(; p; p -= lowbit(p)) sum += tr[p]; return sum; }
#undef lowbit
} bit;

il bool cmp1(const node& x, const node& y) { return x.a ^ y.a ? x.a < y.a : (x.b ^ y.b ? x.b < y.b : x.c < y.c); }
il bool cmp2(const node& x, const node& y) { return x.b ^ y.b ? x.b < y.b : x.c < y.c; }

il void solve(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    solve(l, mid); solve(mid + 1, r);
    sort(lsh + l, lsh + mid + 1, cmp2); sort(lsh + mid + 1, lsh + r + 1, cmp2);
    int ul = l;
    for (int i = mid + 1; i <= r; ++i) {
        while (ul <= mid && lsh[ul].b <= lsh[i].b) bit.add(lsh[ul].c, lsh[ul].val), ++ul;
        lsh[i].ans += bit.query(lsh[i].c);
    }
    for (int i = l; i < ul; ++i) bit.add(lsh[i].c, -lsh[i].val);
}

int main() {
    read(n), read(k);
    for (int i = 1; i <= n; ++i) o[i] = node(read(), read(), read());
    sort(o + 1, o + n + 1, cmp1);
    for (int i = 1; i <= n; ++i) {
        if (o[i] != o[i - 1]) lsh[++cnt] = o[i];
        ++lsh[cnt].val;
    }
    bit.resize(200000);
    solve(1, cnt);
    for (int i = 1; i <= cnt; ++i) f[lsh[i].ans + lsh[i].val - 1] += lsh[i].val;
    for (int i = 0; i < n; ++i) write(f[i]);
    return 0;
}

当然我们在上述提到过 cdq 分治可以化动为静,因此我们可以将点对变成动态的。比如下面这道例题

[CQOI2011] 动态逆序对

给定 \(a_i\),同时给定一个删除元素的顺序,问删除某个元素之前逆序对的个数。

依题意,在删除第 \(i\) 个点之前,有效的点对必须满足 \(tim_j > tim_i,(i<j\land a_i > a_j) \lor (i > j \and a_i<a_j)\)。

这不经典三维偏序?贴出另一种 cdq 分治实现方式

点我看代码
const int N = 1e5 + 5;
int n, m;
ll Ans;
struct node {
    int t, v, i, ans;
    node(){ ans = 0; }
    node(int _t, int _v, int _i) : t(_t), v(_v), i(_i) {}
} a[N];
struct BIT {
#define lowbit(x) (x & (-x))
    int tr[N], n;
    il void resize(const int lim) { n = lim; }
    il void add(int p, const int v) { for(; p <= n; p += lowbit(p)) tr[p] += v; }
    il int query(int p) { int sum = 0; for (; p; p -= lowbit(p)) sum += tr[p]; return sum; }
#undef lowbit
}bit;
il bool cmp1(const node& x, const node& y) { return x.t < y.t; }
il bool cmp2(const node& x, const node& y) { return x.v < y.v; }
il bool cmp3(const node& x, const node& y) { return x.i < y.i; }
il void solve(int l, int r) {
    if (r - l == 1) return;
    int mid = (l + r) >> 1;
    solve(l, mid); solve(mid, r);
    int i = l + 1, j = mid + 1;
    while (i <= mid) {
        while (a[i].v > a[j].v && j <= r) bit.add(a[j].t, 1), ++j;
        a[i].ans += bit.query(m) - bit.query(a[i].t); ++i;
    }
    i = l + 1, j = mid + 1;
    while (i <= mid) {
        while (a[i].v > a[j].v && j <= r) bit.add(a[j].t, -1), ++j;
        ++i;
    }
    i = mid; j = r;
    while (j > mid) {
        while(a[j].v < a[i].v && i > l) bit.add(a[i].t, 1), --i;
        a[j].ans += bit.query(m) - bit.query(a[j].t); --j;
    }
    i = mid; j = r;
    while (j > mid) {
        while(a[j].v < a[i].v && i > l) bit.add(a[i].t, -1), --i;
        --j;
    }
    sort(a + l + 1, a + r + 1, cmp2);
}
int rnk[N];
int main() {
    read(n), read(m);
    for (int i = 1; i <= n; ++i) read(a[i].v), rnk[a[i].v] = i;
    for (int i = 1; i <= m; ++i) a[rnk[read()]].t = i;
    for (int i = 1; i <= n; ++i) if (!a[i].t) a[i].t = m;
    bit.resize(n);
    for (int i = 1; i <= n; ++i) {
        Ans += bit.query(n) - bit.query(a[i].v);
        bit.add(a[i].v, 1);
    }
    for (int i = 1; i <= n; ++i) bit.add(a[i].v, -1);
    solve(0, n);
    sort(a + 1, a + n + 1, cmp1);
    for (int i = 1; i <= m; ++i) {
        write(Ans); Ans -= a[i].ans;
    }
    return 0;
}

我也不知道为什么,反正我用板子题写法没过,换成这种过了。

动态规划

标签:node,le,int,分治,mid,solve,CDQ
From: https://www.cnblogs.com/misterrabbit/p/17242580.html

相关文章

  • [学习笔记] CDQ分治
    引入-分治分治,就是将讲原问题不断细分直到规模小到能够解决,然后一层层向上合并得到答案的过程。归并排序大致思想:把序列拆成左右两部分,分别归并排序,然后使用两个指针......
  • Tree Master (根号分治,离散化)
    题目大意:给出一个树,每次给出2个相同高度的点,然后依次向父亲走,问val[a]*val[b]这些值加起来是多少 思路:直接map映射关联容器,时间复杂度过大根号分治?于是......
  • 【洛谷】P2150 [NOI2015] 寿司晚宴(状压dp+根号分治)
    原题链接题意有序列\(2,3,4\cdotsn\),对于序列中的每一个数,它可以被放入两个集合中的任意一个,或者不选。最后需要满足两个集合间的数两两互质(集合内部的数不需要满足互......
  • 分治算法总结(未完结)
    分治思想:将一个问题分解成若干个结构与原问题相同的子问题。(划分子问题+后处理)一、经典问题1.最大子段和思路:计算左边的最大子段和、右边的最大子段和以及跨越......
  • 点分治 学习笔记
    新知识+1。0x00前言点分治适合处理大规模的树上路径信息问题。0x01引入我们通过洛谷的模板题来引入点分治的概念:P3806【模板】点分治1:给定一棵有n个点的带边......
  • 分治
    乘法的优化暴力的乘法复杂度是\(O(n^2)\)的。如果两个\(n\)位数\(x,y\)相乘(我们保证\(n\)是2的幂),那么我们按位把\(x\)分成\(x_L,x_R\)两半,\(y\)分成\(y_L,y_R\),使得\(x=x......
  • 快速幂——分治思想
    问题:求解pow(x,n)解题思路:1.将n个x相乘,时间复杂度为O(n)2.分治思想:二进制数转化成10进制数的方法:二进制数为n=2k-1 *ak  + 2k-2*ak-1+......
  • 【算法设计-分治】递归与尾递归
    目录1.阶乘尾递归:递归的进一步优化2.斐波那契数列3.最大公约数(GCD)4.上楼梯5.汉诺塔(1)输出移动过程输出移动步数5.汉诺塔(2)输出移动过程输出移动步数6.杨辉三角形7.完......
  • 【算法设计-枚举、分治】素数、约数、质因数分解
    目录1.素数判定2.素数筛选法3.质因数分解4.求一个数的约数5.求两个数的最大公约数(GCD)6.求两个数的最小公倍数(LCM)1.素数判定判定从2到sqrt(n)依次能否把n整除,......
  • F. Timofey and Black-White Tree 2100 (树 根号分治 思维)
    https://codeforces.com/problemset/problem/1790/F题意:给定一棵树,需要将其染为全黑,初始时只有一个点为黑色,给定一个序列c,按招顺序染色,要求每次染色后给出当前任意两黑点......