CONTENT: CDQ分治
DETAIL: 三维偏序、优化DP



BB

作业刚写完系列 tcl 还不知道会不会挂 tcl++

Introduction

CDQ分治用于解决三维偏序问题,在此基础上延申了优化DP、整体二分等
二维偏序就比如说求x1<x2,y1<y2的数目
三维偏序就比如说求x1<x2,y1<y2,z1<z2的数目
显然,二维偏序可以通过排序+树状数组的方式解决,而CDQ分治在此基础上套了一层分治

[CQOI2011]动态逆序对 - Luogu P3157

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

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1

Solution
逆序对本身为二维偏序问题,现在删除元素,如果为每个删除的元素打上被删除时的时间戳,就变成了三维偏序问题,用CDQ分治求解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000 + 15;

struct Node {
    int tim, val;
    bool operator < (const Node& b) const {
        return val < b.val;
    }
};
int tree[N];
Node a[N];
int ans[N];
int mp[N];

inline void init(int n) {
    memset(tree, 0, n * sizeof(int));
}

inline int lowbit(int x) {
    return x & -x;
}

inline void update(int x, int val) {
    for(; x < N; x += lowbit(x)) {
        tree[x] += val;
    }
}

inline int getSum(int x) {
    int sum = 0;
    for(; x > 0; x -= lowbit(x)) {
        sum += tree[x];
    }
    return sum;
}

void cdq(int l, int r, int m) {
    if(l >= r) {
        return;
    }
    int mid = (l + r) >> 1;
    cdq(l, mid, m);
    cdq(mid + 1, r, m);
    sort(a + l, a + mid + 1);
    sort(a + mid + 1, a + r + 1);

    static stack<int> stk;
    for(int j = l, i = mid + 1; j <= mid; j++) {
        while(i <= r && a[j].val > a[i].val) {
            update(a[i].tim, 1);
            stk.emplace(a[i].tim);
            i++;
        }
        ans[a[j].tim] += (getSum(m + 1) - getSum(a[j].tim));
    }
    while(!stk.empty()) {
        update(stk.top(), -1);
        stk.pop();
    }

    for(int i = r, j = mid; i >= mid + 1; i--) {
        while(j >= l && a[j].val > a[i].val) {
            update(a[j].tim, 1);
            stk.emplace(a[j].tim);
            j--;
        }
        ans[a[i].tim] += (getSum(m + 1) - getSum(a[i].tim));
    }
    while(!stk.empty()) {
        update(stk.top(), -1);
        stk.pop();
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n, m;
    while(cin >> n >> m) {
        for(int i = 1; i <= n; i++) {
            cin >> a[i].val;
            mp[a[i].val] = i;
            a[i].tim = m + 1;
        }
        for(int i = 1, ele; i <= m; i++) {
            cin >> ele;
            a[mp[ele]].tim = i;
        }

        init(n + 2);
        ll allInversions = 0;
        for(int i = n; i >= 1; i--) {
            allInversions += getSum(a[i].val);
            update(a[i].val, 1);
        }

        init(N - 1);
        cdq(1, n, m);
        for(int i = 1; i <= m; i++) {
            cout << allInversions << "\n";
            allInversions -= ans[i];
        }
        cout.flush();
    }
}


[SDOI2011]拦截导弹 - Luogu P2487

Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。
我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。
Sample Input

4
3 30
4 40
6 60
3 30

Sample Output

2
0.33333 0.33333 0.33333 1.00000

Solution
显然是二维LIS问题,列出dp方程为 dp[i] = max{[j<i][xj>xi][yj>yi] dp[j]} + 1
满足三维偏序,不妨用CDQ分治解决,先递归左边,再处理中间,最后递归右边(左右中的顺序会右边的某些值不是最终值但是先被计算了的问题!)
对于每一个元素成为LIS中元素的概率,则需要在DP时同时维护数量。正反两遍DP(CDQ)后,枚举每个元素,看看正反的DP值相加减1是否等于LIS,也就是看看接上去能不能成为LIS,能的话根据乘法原理,成为LIS的总次数为数量相乘值,概率则为该值除以总数量
最后,总数量怎么求?DP时已经维护了数量,只需要在求概率前枚举每个元素,看正DP值(或反DP值)为LIS时累加数量即可,该值为总数量

#include <bits/stdc++.h>
using namespace std;
const int N = (int)5e4 + 15;

struct info {
    int h, v, idx, dp;
    double cnt;
};
struct Tree {
    int len;
    double sum;
    Tree(): len(0), sum(0) {}
    Tree(int len, double sum): len(len), sum(sum) {}
};
info a[N], b[N];
int mph[N], mpv[N];
Tree tree[N];

inline bool cmp(const info& a, const info& b) {
    return a.idx < b.idx;
}
inline bool cmp1 (const info& a, const info& b) {
    return a.h != b.h ? a.h < b.h : a.v < b.v;
}

inline int lowbit(int x) {
    return x & -x;
}

inline void update(int x, int len, double sum) {
    for(; x < N; x += lowbit(x)) {
        if(tree[x].len < len) {
            tree[x] = Tree{len, sum};
        } else if(tree[x].len == len) {
            tree[x].sum += sum;
        }
    }
}

inline Tree getSum(int x) {
    Tree ret;
    for(; x > 0; x -= lowbit(x)) {
        if(tree[x].len > ret.len) {
            ret = tree[x];
        } else if(tree[x].len == ret.len) {
            ret.sum += tree[x].sum;
        }
    }
    return ret;
}

inline void clearTree(int x) {
    for(; x < N; x += lowbit(x)) {
        tree[x] = Tree{0, 0};
    }
}

inline void cdq(int l, int r, info* a) {
    if(r <= l) {
        return;
    }
    int mid = (l + r) >> 1;
    cdq(l, mid, a);

    sort(a + l, a + mid + 1, cmp1);
    sort(a + mid + 1, a + r + 1, cmp1);
    int st = l;
    for(int i = mid + 1; i <= r; i++) {
        while(a[st].h <= a[i].h && st <= mid) {
            update(a[st].v, a[st].dp, a[st].cnt);
            st++;
        }
        Tree res = getSum(a[i].v);
        if(res.len == 0) {
            continue;
        }
        if(res.len + 1 > a[i].dp) {
            a[i].dp = res.len + 1;
            a[i].cnt = res.sum;
        } else if(res.len + 1 == a[i].dp) {
            a[i].cnt += res.sum;
        }
    }

    for(int i = l; i < st; i++) {
        clearTree(a[i].v);
    }

    sort(a + mid + 1, a + r + 1, cmp);

    cdq(mid + 1, r, a);
}

int main() {
    int n;
    while(~scanf("%d", &n)) {
        for(int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i].h, &a[i].v);
            a[i].idx = i, b[i].idx = n - i + 1;
            a[i].dp = b[i].dp = 1;
            a[i].cnt = b[i].cnt = 1;
            mph[i] = a[i].h;
            mpv[i] = a[i].v;
        }
        sort(mph + 1, mph + n + 1);
        sort(mpv + 1, mpv + n + 1);
        int toth = unique(mph + 1, mph + n + 1) - mph - 1;
        int totv = unique(mpv + 1, mpv + n + 1) - mpv - 1;
        for(int i = 1; i <= n; i++) {
            a[i].h = lower_bound(mph + 1, mph + toth + 1, a[i].h) - mph;
            a[i].v = lower_bound(mpv + 1, mpv + totv + 1, a[i].v) - mpv;
        }

        for(int i = 1; i <= n; i++) {
            b[i].h = a[i].h;
            b[i].v = a[i].v;
            a[i].h = toth - a[i].h + 1;
            a[i].v = totv - a[i].v + 1;
        }
        reverse(b + 1, b + n + 1);

        cdq(1, n, a);
        sort(a + 1, a + n + 1, cmp);

        int maxLen = 0;
        for(int i = 1; i <= n; i++) {
            maxLen = max(maxLen, a[i].dp);
        }
        double sumCnt = 0;
        for(int i = 1; i <= n; i++) {
            if(a[i].dp == maxLen) {
                sumCnt += a[i].cnt;
            }
        }

        cdq(1, n, b);
        sort(b + 1, b + n + 1, cmp);

        printf("%d\n", maxLen);
        for(int i = 1; i <= n; i++) {
            if(a[i].dp + b[n - i + 1].dp - 1 == maxLen) {
                printf("%.5f ", a[i].cnt * b[n - i + 1].cnt / sumCnt);
            } else {
                printf("0.00000 ");
            }
        }
        puts("");
    }
}