进阶笔记·CDQ分治

概念

由于博主太懒,详细讲解见详细讲解

例题:P3810 【模板】三维偏序(陌上花开)

原题地址
在这里插入图片描述
在这里插入图片描述

代码:
三维CDQ分治所白了就是一个并归排序,排序时用树状数组维护操作(前置技能点:树状数组求逆序对,感觉和权值线段树相似)
代码在详细讲解中,不再赘述。

Travel Guide

原题地址
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码:
人话翻译:给一张无向图,定义一个点的三个属性为到0、1、2三点的距离,定义q_i<q_j为三个属性都严格小于,当存在点j小于点i时,i点被覆盖。询问不会被覆盖的点的个数。
求属性用dijsktra,求个数用cdq

#include <bits/stdc++.h>
#define ll long long
#define dou double
#define pb emplace_back
#define mp make_pair
#define sot(a,b) sort(a+1,a+1+b)
#define eps 1e-8
#define int_inf 0x3f3f3f3f
#define ll_inf 0x7f7f7f7f7f7f7f7f
#define lson (curpos<<1)
#define rson (curpos<<1|1)
using namespace std;

const int maxn = 1e5 + 10;
struct Node 
{
    int x, y, z, id, cnt;
    bool operator<(const Node &rhs)const 
    {
        return x != rhs.x ? x < rhs.x : y != rhs.y ? y < rhs.y : z < rhs.z;
    }
} info[maxn], temp[maxn];
int n, m, num = 0, sum = 0, a[maxn], dis1[maxn], dis2[maxn], dis3[maxn], vis[maxn], head[maxn];
vector<pair<int, int>>g[maxn];
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>q;

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

void add(int pos, int val) {
    while (pos < maxn) a[pos] += val, pos += lowbit(pos);
}

int query(int pos) {
    int ret = 0;
    while (pos) ret += a[pos], pos -= lowbit(pos);
    return ret;
}

void dijkstra(int *dis, int st) {
    for (int i = 0; i <= n; ++i) dis[i] = int_inf;
    dis[st] = 0;
    while (q.size()) q.pop();
    q.push(mp(dis[st], st));
    while (q.size()) {
        pair<int, int> temp = q.top(); q.pop();
        if (temp.first > dis[temp.second]) continue;
        for (int i = 0; i < (int)g[temp.second].size(); ++i) {
            if (dis[temp.second] + g[temp.second][i].first < dis[g[temp.second][i].second]) {
                dis[g[temp.second][i].second] = dis[temp.second] + g[temp.second][i].first;
                q.push(mp(dis[g[temp.second][i].second], g[temp.second][i].second));
            }
        }
    }
}

void init() 
{
    for (int i = 0; i < maxn; i++) vis[i] = 0;
}

void cdq(int l, int r) {
    if (r - l <= 1) return;
    int mid = (r + l) >> 1;
    cdq(l, mid); cdq(mid, r);
    int lPtr = l, rPtr = mid, cnt = 0;
    while (lPtr < mid && rPtr < r) {
        if (info[lPtr].y <= info[rPtr].y) {
            add(info[lPtr].z, 1);
            temp[cnt++] = info[lPtr++];
        } else {
            int curr = query(info[rPtr].z);
            if (curr) vis[info[rPtr].id] = info[rPtr].cnt;
            temp[cnt++] = info[rPtr++];
        }
    }
    while (rPtr < r) {
        int curr = query(info[rPtr].z);
        if (curr) vis[info[rPtr].id] = info[rPtr].cnt;
        temp[cnt++] = info[rPtr++];
    }
    for (int i = l; i < lPtr; ++i) add(info[i].z, -1);
    while (lPtr < mid) temp[cnt++] = info[lPtr++];
    for (int i = 0; i < cnt; ++i) info[i + l] = temp[i];
}

int main() 
{
    scanf("%d%d", &n, &m); sum = n, num = 0;
    while (m--) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].pb(mp(w, v)); g[v].pb(mp(w, u));
    }
    // maintain distance
    dijkstra(dis1, 0); dijkstra(dis2, 1); dijkstra(dis3, 2);
    for (int i = 0; i < n; ++i)  info[i].x = dis1[i], info[i].y = dis2[i], info[i].z = dis3[i], head[++num] = info[i].z;
    sort(head + 1, head + 1 + num);
    // 离散化处理
    num = unique(head + 1, head + 1 + num) - head - 1;
    for (int i = 0; i < n; ++i) info[i].z = lower_bound(head + 1, head + 1 + num, info[i].z) - head;
    sort(info, info + n);
    int p = 1, tot = 0;
    // spj equal
    for (int i = 0; i < n; ++i) {
        if (info[i].x == info[i + 1].x && info[i].y == info[i + 1].y && info[i].z == info[i + 1].z) {
            p++; continue;
        }
        info[i].cnt = p; info[tot++] = info[i]; p = 1;
    }
    n = tot;
    for (int i = 0; i < n; ++i) info[i].id = i;
    cdq(0, n);
    for (int i = 0; i < n; ++i) sum -= vis[i];
    printf("%d\n", sum);
}

总体思路就是把被覆盖的点标记出来。
求最长上升序列的题参考博客
中序遍历,然后dp

讨论:dilworth定理
参考博客
博文里的最少全序集是否可以理解为:最长全序集的个数?

举例:1 2 3 2 3
定义二元关系<=
则最少全序集个数为2(1,2,3),(2,3)
(解释为啥只有两个:每次都要有新元素,最多用两个?参考导弹拦截)

最长反链长度为2(3,2)

另一篇

参考cf round 617 E2
qsc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值