【算法】CDQ分治 -- 三维偏序 & 动态逆序对

初次接触CDQ分治,感觉真的挺厉害的。

整体思路即分而治之,再用之前处理出来的答案统计之后的答案。

大概流程是:

对于区间 l ~ r :

1.处理 l ~mid, mid + 1 ~ r 的答案

2.分别排序规整

3.计算 l ~ mid 中每一个数对 mid + 1 ~ r 中的答案的贡献, 累加

4.得到区间l ~ r的答案

CDQ分治我一共也才做了两道题目, 就一起整理在这里了。大体都差不多,CDQ+树状数组分别维护两个维度。

1.三维偏序

#include <bits/stdc++.h>
using namespace std;
#define maxn 3000000
#define lowbit(x) x &(-x)
int n, k, tot, ans[maxn], c[maxn];
struct node
{
    int a, b, c, ans, cnt;
}num[maxn], a[maxn];

int read()
{
    int x = 0, k = 1;
    char c;
    c = getchar();
    while(c < ‘0‘ || c > ‘9‘) { if(c == ‘-‘) k = -1; c = getchar();}
    while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar();
    return x * k;
}

bool cmp(node a, node b)
{
    if(a.a != b.a) return a.a < b.a;
    if(a.b != b.b) return a.b < b.b;
    return a.c < b.c;
}

bool cmp2(node a, node b)
{
    if(a.b != b.b) return a.b < b.b;
    return a.c < b.c;
}

void Update(int x, int v)
{
    for(int i = x; i <= k; i += lowbit(i))
        c[i] += v;
}

int Query(int x)
{
    int ans = 0;
    for(int i = x; i; i -= lowbit(i))
        ans += c[i];
    return ans;
}

void cdq(int l, int r)
{
    int mid = (l + r) >> 1;
    if(r - l >= 2) cdq(l, mid), cdq(mid + 1, r);
    if(r == l) return;
    sort(num + l, num + mid + 1, cmp2);
    sort(num + mid + 1, num + r + 1, cmp2);
    int i = l, j = mid + 1;
    while(i <= mid && j <= r)
    {
        if(num[i].b <= num[j].b) Update(num[i].c, num[i].cnt), i ++;
        else num[j].ans += Query(num[j].c), j ++;
    }
    while(i <= mid) Update(num[i].c, num[i].cnt), i ++;
    while(j <= r) num[j].ans += Query(num[j].c), j ++;
    for(int i = l; i <= mid; i ++) Update(num[i].c, -num[i].cnt);
}

int main()
{
    n = read(), k = read();
    for(int i = 1; i <= n; i ++)
        a[i].a = read(), a[i].b = read(), a[i].c = read();
    sort(a + 1, a + 1 + n, cmp);
    for(int i = 1; i <= n;)
    {
        int j = 1;
        while(i + j <= n && a[i].a == a[i + j].a && a[i].b == a[i + j].b && a[i].c == a[i + j].c) j ++;
        num[++ tot] = a[i];
        num[tot].cnt = j;
        i += j;
    }
    cdq(1, tot);
    for(int i = 1; i <= tot; i ++) ans[num[i].ans + num[i].cnt - 1] += num[i].cnt;
    for(int i = 0; i < n; i ++) printf("%d\n", ans[i]);
    return 0;
}

2.动态逆序对

#include <bits/stdc++.h>
using namespace std;
#define maxn 2000000
#define ll long long
#define lowbit(x) x & (-x)
int n, m, timer, a[maxn], b[maxn], d[maxn], t[maxn];
ll ans[maxn], c[maxn];
struct node
{
    int t, num, pl;
    ll ans;
}w[maxn];

int read()
{
    int x = 0, k = 1;
    char c;
    c = getchar();
    while(c < ‘0‘ || c > ‘9‘) { if(c == ‘-‘) k = -1; c = getchar();}
    while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar();
    return x * k;
}

bool cmp(node a, node b)
{
    if(a.t != b.t) return a.t < b.t;
    else return a.pl < b.pl;
}

bool cmp2(node a, node b)
{
    return a.pl < b.pl;
}

bool cmp3(node a, node b)
{
    return a.pl > b.pl;
}

void add(int x, int num)
{
    for(int i = x; i <= n; i += lowbit(i))
        c[i] += num;
}

ll query(int x)
{
    ll ans = 0;
    for(int i = x; i; i -= lowbit(i))
        ans += c[i];
    return ans;
}

void CDQ(int l, int r)//位置在我之前,num>我的
{
    int mid = (l + r) >> 1;
    if(r - l >= 2) CDQ(l, mid), CDQ(mid + 1, r);
    if(l == r) return;
    sort(w + l, w + 1 + mid, cmp2);
    sort(w + mid + 1, w + r + 1, cmp2);
    int i = l, j = mid + 1;
    while(i <= mid && j <= r)
    {
        if(w[i].pl < w[j].pl) add(w[i].num, 1), i ++;
        else w[j].ans += (query(n) - query(w[j].num)), j ++;
    }
    while(i <= mid) add(w[i].num, 1), i ++;
    while(j <= r) w[j].ans += (query(n) - query(w[j].num)), j ++;
    for(int i = l; i <= mid; i ++)
        add(w[i].num, -1);
}

void CDQ2(int l, int r)//位置在我之后,num<我的
{
    int mid = (l + r) >> 1;
    if(r - l >= 2) CDQ2(l, mid), CDQ2(mid + 1, r);
    if(l == r) return;
    sort(w + l, w + 1 + mid, cmp3);
    sort(w + mid + 1, w + r + 1, cmp3);
    int i = l, j = mid + 1;
    while(i <= mid && j <= r)
    {
        if(w[i].pl > w[j].pl) add(w[i].num, 1), i ++;
        else w[j].ans += (query(w[j].num)), j ++;
    }
    while(i <= mid) add(w[i].num, 1), i ++;
    while(j <= r) w[j].ans += (query(w[j].num)), j ++;
    for(int i = l; i <= mid; i ++)
        add(w[i].num, -1);
}

int main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i ++)
    {
        a[i] = read();
        b[a[i]] = i;
    }
    timer = m;
    for(int i = 1; i <= m; i ++)
    {
        d[i] = read();
        t[b[d[i]]] = timer --;
    }
    for(int i = 1; i <= n; i ++) w[i].t = t[i], w[i].num = a[i], w[i].pl = i;
    sort(w + 1, w + 1 + n, cmp);
    CDQ(1, n);
    for(int i = 1; i <= n; i ++) ans[w[i].t] += w[i].ans;
    for(int i = 1; i <= n; i ++) w[i].t = t[i], w[i].num = a[i], w[i].pl = i, w[i].ans = 0;
    sort(w + 1, w + 1 + n, cmp);
    memset(c, 0, sizeof(c));
    CDQ2(1, n);
    for(int i = 1; i <= n; i ++) ans[w[i].t] += w[i].ans;
    for(int i = 1; i <= m; i ++) ans[i] += ans[i - 1];
    for(int i = m; i >= 1; i --) printf("%lld\n", ans[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/twilight-sx/p/8451991.html

时间: 2024-10-13 02:03:42

【算法】CDQ分治 -- 三维偏序 & 动态逆序对的相关文章

BZOJ 3262: 陌上花开 [CDQ分治 三维偏序]

Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要美丽,当且仅当Sa>=Sb,Ca>=Cb,Ma>=Mb.显然,两朵花可能有同样的属性.需要统计出评出每个等级的花的数量. Input 第一行为N,K (1 <= N <= 100,000, 1 <= K <= 200,000 ), 分别表示花的数量和最大属性值. 以下N行,每

BZOJ3262/洛谷P3810 陌上花开 CDQ分治 三维偏序 树状数组

原文链接http://www.cnblogs.com/zhouzhendong/p/8672131.html 题目传送门 - BZOJ3262 题目传送门 - 落谷P3810 题意 有$n$个元素,第$i$个元素有$a_i$.$b_i$.$c_i$三个属性,设$f(i)$表示满足$a_j\leq a_i$且$b_j\leq b_i$且$c_j\leq c_i$的$j$的数量.对于$d\in [0,n)$,求$f(i)=d$的数量. $n\leq 100000,max\{a_i,b_i,c_i|i

CDQ分治 三维偏序

这应该是一道CDQ分治的入门题目 我们知道,二维度的偏序问题直接通过,树状数组就可以实现了,但是三维如何实现呢? 我记得以前了解过一个小故事,应该就是分治的. 一个皇帝,想给部下分配任务,但是部下太多,他也无从下手于是他这个任务分给宰相,宰相也不怎么清楚,于是他又分给他的手下,这么一直分啊分啊,分到每一个人头顶上的时候 每个人知道自己要干什么,于是他把它的信息交给他的上级,上级有了这些数据后,他处理了交给他的上级...这么一直交啊...国王最后成功的分配这些任务. CDQ分治也是一样,在这里,首

BZOJ.1935.[SHOI2007]Tree园丁的烦恼(CDQ分治 三维偏序)

题目链接 矩形查询可以拆成四个点的前缀和查询(树套树显然 但是空间不够) 每个操作表示为(t,x,y),t默认有序,对x分治,y用树状数组维护 初始赋值需要靠修改操作实现. //119964kb 4380ms #include <cstdio> #include <cctype> #include <algorithm> #define gc() getchar() #define lb(x) (x)&-(x) const int N=5e5+5; int n,

P3157 动态逆序对 CDQ分治

动态逆序对 CDQ分治 传送门:https://www.luogu.org/problemnew/show/P3157 题意: 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 题解: 这个题是告诉你如何将一个问题转换为三维偏序问题 首先,求解逆序对,那么a.val>b.val,删除一个元素的时间是t,a.t<b.t,这个元素对应的原序列中的位置为

P3157 [CQOI2011]动态逆序对 (CDQ解决三维偏序问题)

P3157 [CQOI2011]动态逆序对 题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入格式 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出格式 输出包含m行,依次为删除每个元素之前,逆序对的个数. 输入输出样例 输入

【Luogu1393】动态逆序对(CDQ分治)

[Luogu1393]动态逆序对(CDQ分治) 题面 题目描述 对于给定的一段正整数序列,我们定义它的逆序对的个数为序列中ai>aj且i < j的有序对(i,j)的个数.你需要计算出一个序列的逆序对组数及其删去其中的某个数的逆序对组数. 输入输出格式 输入格式: 第一行,两个数n,m,表示序列中有n个数,要删去m个数 第二行n个数,表示给定的序列. 第三行m个数,第i个数di表示要删去原序列中的第di个数. 输出格式: 一行m+1个数.第一个数表示给定序列的逆序对组数,第i+1个数表示删去第d

【BZOJ3295】[Cqoi2011]动态逆序对 cdq分治

[BZOJ3295][Cqoi2011]动态逆序对 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. Output 输出包含m行,依次为删除每个元素之前,逆序对的

BZOJ 3295: [Cqoi2011]动态逆序对 cdq分治

https://www.lydsy.com/JudgeOnline/problem.php?id=3295 这个妹妹我曾见过的~~~ 之前应该在校内oj写了,似乎还写过题解?发现没写博客就重新水一遍代码水一篇博客好了. 把找逆序对的过程想成一个一个往里塞数字然后找每个数字可以组成的逆序对,把最后要去掉的几个也想成往里塞,所以加一个时间维度用cdq求三维偏序. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorith