支配树算法

简介

在一个有向图里,可以有环也可以无环,当一个点u到另一个点v的所有可行路径都必经一个点w,那么可以称u被w支配,所以一个点必定会被一个点支配(除了顶点)
支配树是一个单源算法(学习支配树的时候因为不知道这个被坑了4天,感谢某位大佬抬了一手)

树形图

因为该图本身就是树状,所以这颗树本身就是自己的支配树,显然根节点到任意非根节点都需要经过树路径上的所有点,这些点都是其支配点。

DAG图下建支配树

由于DAG图是有向无环图,所以只需要进行拓扑序建树即可,可以快速得到支配树的上层结构。
例如得到了1-n每个点的拓扑序,也得到了拓扑排序后的点顺序,然后按照拓扑序进行遍历,但遍历到x点的时候,1-(x-1)点的结构已经建立完了,然后通过遍历他的所有父亲节点,得到其中的LCA,然后将他自己连到LCA上,由于是一边建树一边查询LCA,所以用到倍增LCA进行动态LCA。
由于是有向图,所以可以同时存其反向图,遍历反向图进行拓扑排序,正向图进行建树。
Graph参见 Graph图结构体

const int MAX_DEP = 20;

// 注意0,1点的边界问题
struct DominatorTree {
    int deg[N]; // 入度
    int dep[N]; //
    int dfn[N];
    int st[N];
    int tot;

    // 拓扑序, 要保证root是入度为0
    void bfs(Graph &gh, int root) {
        queue<int> q;
        q.push(root);
        tot = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            dfn[u] = ++tot;
            st[tot] = u;
            forg(i, gh.head[u], gh.eg) {
                int v = gh.eg[i].e;
                if ((--deg[v]) == 0) {
                    q.push(v);
                }
            }
        }
    }

    // 倍增2^k的父亲
    int fa[N][MAX_DEP];

    // 倍增LCA
    int lca(int u, int v) {
        if (dep[u] > dep[v]) {
            swap(u, v);
        }
        int hu = dep[u], hv = dep[v];
        int tu = u, tv = v;
        for (int det = hv - hu, i = 0; det; det >>= 1, i++) {
            if (det & 1)
                tv = fa[tv][i];
        }
        if (tu == tv) {
            return tu;
        }
        for (int i = MAX_DEP - 1; i >= 0; i--) {
            if (fa[tu][i] == fa[tv][i]) {
                continue;
            }
            tu = fa[tu][i];
            tv = fa[tv][i];
        }
        return fa[tu][0];
    }

    // 动态更新节点的父亲属性
    void lineFa(int u, int v) {
        fa[u][0] = v;
        for (int i = 1; i < MAX_DEP; i++) {
            v = fa[u][i] = fa[v][i - 1];
        }
    }

    // 建树, op是gh的反向图,用来寻找其父亲
    void build(Graph &gh, Graph &op, int n, int root) {
        memcpy(deg, gh.deg, sizeof(int) * (n + 1));
        bfs(gh, root);
        for (int k = 1; k <= tot; k++) {
            int u = st[k], fath = -1;
            dep[u] = 0;
            for (int i = op.head[u]; ~i; i = op.eg[i].nxt) {
                int v = op.eg[i].e;
                if (dfn[v] > dfn[u]) continue;
                fath = (fath == -1 ? v : lca(fath, v));
            }
            if (fath == -1) fath = u;
            lineFa(u, fath);
            dep[u] = dep[fath] + 1;
        }
    }
} dtree;

有向图

Lengauer-Tarjan算法(待学)

例题

再贴一个被折磨了4天的那道题(顺便学了下倍增LCA),HDU杭电2019暑期多校第三场B题
一个DAG图下每次询问给出的两个点u,v到所有出度为0的点的支配点个数
这题通过反向建边+虚点连接所有出度为0的点建立支配树,每次查询的时候,查询树上的u,v两点的深度,容斥减去其LCA点的深度,就是其支配点的个数
HDU 6604 Blow up the city

// 巨菜的ACMer-Happy233

#include <bits/stdc++.h>

using namespace std;

//-----
typedef double db;
typedef long long ll;
typedef unsigned int ui;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
#define mp make_pair
#define fi first
#define se second
#define pw(x) (1ll << (x))
#define bt(x, i) ((x >> i) & 1)
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define rep(i, l, r) for(int i=(l);i<(r);++i)
#define per(i, l, r) for(int i=(r)-1;i>=(l);--i)
#define sf(x) scanf("%d", &(x))
#ifndef ACM_LOCAL
#define endl '\n'
#endif

const double pi = acos(-1);
const int MOD = int(998244353);

#define forg(i, h, eg) for(int i = (h); ~i; i = (eg[i]).nxt)

struct Edge {
    int e, nxt;
    ll v;

    Edge() = default;

    Edge(int a, ll b, int c = 0) : e(a), v(b), nxt(c) {}

    bool operator<(const Edge &a) const {
        return (a.v == v ? e < a.e : v < a.v);
    }
};

const ll INF = ll(1e11);
const int N = int(1e5 + 10);
const int M = int(3e5 + 10);

struct Graph {
    Edge eg[M];
    int deg[N];
    int head[N];
    int cnt;

    void init(int n) {
        memset(head, -1, sizeof(int) * ++n);
        memset(deg, 0, sizeof(int) * n);
        cnt = 0;
    }

    inline void addEdge(int x, int y, ll v = 0) {
        eg[cnt] = Edge(y, v, head[x]);
        head[x] = cnt++;
        deg[y]++;
    }
};

const int MAX_DEP = 20;

struct DominatorTree {
    int deg[N]; // 入度
    int dep[N]; //
    int dfn[N];
    int st[N];
    int tot;

    // 拓扑序, 要保证root是入度为0
    void bfs(Graph &gh, int root) {
        queue<int> q;
        q.push(root);
        tot = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            dfn[u] = ++tot;
            st[tot] = u;
            forg(i, gh.head[u], gh.eg) {
                int v = gh.eg[i].e;
                if ((--deg[v]) == 0) {
                    q.push(v);
                }
            }
        }
    }

    // 倍增2^k的父亲
    int fa[N][MAX_DEP];

    // 倍增LCA
    int lca(int u, int v) {
        if (dep[u] > dep[v]) {
            swap(u, v);
        }
        int hu = dep[u], hv = dep[v];
        int tu = u, tv = v;
        for (int det = hv - hu, i = 0; det; det >>= 1, i++) {
            if (det & 1)
                tv = fa[tv][i];
        }
        if (tu == tv) {
            return tu;
        }
        for (int i = MAX_DEP - 1; i >= 0; i--) {
            if (fa[tu][i] == fa[tv][i]) {
                continue;
            }
            tu = fa[tu][i];
            tv = fa[tv][i];
        }
        return fa[tu][0];
    }

    void lineFa(int u, int v) {
        fa[u][0] = v;
        for (int i = 1; i < MAX_DEP; i++) {
            v = fa[u][i] = fa[v][i - 1];
        }
    }

    void build(Graph &gh, Graph &op, int n, int root) {
        memcpy(deg, gh.deg, sizeof(int) * (n + 1));
        bfs(gh, root);
        for (int k = 1; k <= tot; k++) {
            int u = st[k], fath = -1;
            dep[u] = 0;
            for (int i = op.head[u]; ~i; i = op.eg[i].nxt) {
                int v = op.eg[i].e;
                if (dfn[v] > dfn[u]) continue;
                fath = (fath == -1 ? v : lca(fath, v));
            }
            if (fath == -1) fath = u;
            lineFa(u, fath);
            dep[u] = dep[fath] + 1;
        }
    }
} dtree;

Graph gh, op;

void solve() {
    int n, m;
    while (cin >> n >> m) {
        gh.init(n);
        op.init(n);
        rep(i, 0, m) {
            int a, b;
            cin >> a >> b;
            gh.addEdge(b, a);
            op.addEdge(a, b);
        }
        for (int i = 1; i <= n; i++) {
            if (gh.deg[i] == 0) {
                gh.addEdge(0, i);
                op.addEdge(i, 0);
            }
        }
        dtree.build(gh, op, n, 0);
        int q;
        cin >> q;
        while (q--) {
            int u, v;
            cin >> u >> v;
            int f = dtree.lca(u, v);
            int ans = dtree.dep[u] + dtree.dep[v] - dtree.dep[f] - 1;
            cout << ans << endl;
        }
    }
}

int main() {
#ifdef ACM_LOCAL
    freopen("./data/1.in", "r", stdin);
    // freopen("./data/std.out", "w", stdout);
#else
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#endif

#ifdef ACM_LOCAL
    auto start = clock();
#endif
    int t;
    cin >> t;
    while (t--)
        solve();
#ifdef ACM_LOCAL
    auto end = clock();
    cerr << "Run Time: " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值