AHOI2005 航线规划

· · 个人记录

AHOI2005 航线规划

洛谷连接

先考虑没有删边的情况:

如果我们把所有的环都缩成点,那么整张图就变成了一棵树,环边都不可能是关键边,而树边可能是(要在两点间路径上),所以一开始直接点双(边双?)缩点,然后建一棵树直接 lca 询问两点间边数即可。

但是如果有了删边,那么每次删完边,图的形状都会发生或大或小的变化,那么(似乎 lct 可以?考试的时候想到了,但没学过 lct 嘤嘤嘤)就不好维护了。。。

这里我们仔细读题,发现题目给出了两个条件:

  1. 任何时候保证图连通
  2. 保证删掉的边不会加上

第一个条件告诉我们不用重构树。

第二个条件告诉我们没有相反操作,也就是说我们不需要维护 两种互相抵消的操作 。

这样,每个删掉的边的影响就是 永久 的,所以如果一开始把所有边都删掉,那么就是最后的情况。

考虑离线,发现在一棵树上,加入一条边会导致两点间路径上所有的边都不是关键边。

流程:

  1. 输入
  2. 构造出树(用没删掉的边)
  3. 把所有没删掉的边又不是树边的边加入(操作与加边一模一样)
  4. 倒着维护处答案

树上两点间路径设置(为 0 ) 以及求和,重剖 + 线段树 暴力。。。

也可以 并查集 + dfs 序 + 树状数组 维护。

(加边:一条边设为 0 后就与其父亲合并,之后每次直接用“蜗牛爬”LCA更新,并且其会对子树内的点往根上的权值和造成 -1 影响,用树状数组 + dfs 序,合并用并查集,查询直接找到 lca (注意这里不能用蜗牛爬!!!)然后加加减减就行)

代码

(并查集的可以看看 orangejuice9 的)

#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define ll long long
#define reg register
#define rep(i, a, b) for (reg int i = (a), i##end = (b); i <= i##end; ++i)
#define dep(i, a, b) for (reg int i = (a), i##end = (b); i >= i##end; --i)

template <typename _typer> inline _typer read() {
    _typer init = 0;
    char ch = getchar(), k = 0;
    for ( ; !isdigit(ch); ch = getchar()) k = (ch == '-');
    for ( ; isdigit(ch); ch = getchar())
        init = (init << 3) + (init << 1) + (ch ^ 48);
    return k ? -init : init;
}
const ll N = 40005, INF = 1e9;
const ll M = 100005;

int n, m, q;

struct EDGE {
    int to, nex;
} edge[N << 1], total[M];
int head[N], cntedge, mark[M], edgemark[M];
void Addedge(int u, int v) {
    edge[++cntedge] = (EDGE) { v, head[u] };
    head[u] = cntedge;
}

int Dep[N], Fa[N];
int Top[N], Siz[N], Son[N];
int Ldfn[N], Rdfn[N], cntDfs;
void Dfs1(int u, int fa) {
    Dep[u] = Dep[Fa[u] = fa] + 1;
    Siz[u] = 1, Son[u] = -1;
    for (int i = head[u], v; i; i = edge[i].nex) {
        v = edge[i].to;
        if (v == fa) continue;
        Dfs1(v, u);
        Siz[u] += Siz[v];
        if (Son[u] == -1 || Siz[v] > Siz[Son[u]]) Son[u] = v;
    }
}
void Dfs2(int u, int tp) {
    Top[u] = tp;
    Ldfn[u] = ++cntDfs;
    if (Son[u] != -1) Dfs2(Son[u], tp);
    for (int i = head[u], v; i; i = edge[i].nex) {
        v = edge[i].to;
        if (v == Fa[u] || v == Son[u]) continue;
        Dfs2(v, v);
    }
    Rdfn[u] = cntDfs;
}

int Sum[N << 2], tag[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void Pushup(int p) {
    Sum[p] = Sum[ls] + Sum[rs];
}
void Pushdown(int p) {
    if (~tag[p]) {
        Sum[ls] = Sum[rs] = 0;
        tag[ls] = tag[rs] = 0;
        tag[p] = -1;
    }
}
void Built(int p, int l, int r) {
    tag[p] = -1, Sum[p] = r - l + 1;
    if (l == 1) --Sum[p];
    if (l == r) return ;
    int mid = (l + r) >> 1;
    Built(ls, l, mid); Built(rs, mid + 1, r);
}
void Upd(int p, int l, int r, int L, int R) {
    if (L > R) return ;
    if (L == l && r == R) {
        tag[p] = Sum[p] = 0;
        return ;
    }
    Pushdown(p);
    int mid = (l + r) >> 1;
    if (R <= mid) Upd(ls, l, mid, L, R);
    else if (L > mid) Upd(rs, mid + 1, r, L, R);
    else Upd(ls, l, mid, L, mid), Upd(rs, mid + 1, r, mid + 1, R);
    Pushup(p);
}
int Que(int p, int l, int r, int L, int R) {
    if (L > R) return 0;
    if (L == l && r == R) return Sum[p];
    Pushdown(p);
    int mid = (l + r) >> 1;
    if (R <= mid) return Que(ls, l, mid, L, R);
    if (L > mid) return Que(rs, mid + 1, r, L, R);
    return Que(ls, l, mid, L, mid) + Que(rs, mid + 1, r, mid + 1, R);
}

void Update(int x, int y) {
    for ( ; Top[x] != Top[y];
        Upd(1, 1, n, Ldfn[Top[y]], Ldfn[y]), y = Fa[Top[y]])
        if (Dep[Top[x]] > Dep[Top[y]]) swap(x, y);
    if (Dep[x] > Dep[y]) swap(x, y);
    Upd(1, 1, n, Ldfn[x] + 1, Ldfn[y]);
}
int Query(int x, int y) {
    int Ans = 0;
    for ( ; Top[x] != Top[y];
        Ans += Que(1, 1, n, Ldfn[Top[y]], Ldfn[y]), y = Fa[Top[y]])
        if (Dep[Top[x]] > Dep[Top[y]]) swap(x, y);
    if (Dep[x] > Dep[y]) swap(x, y);
    return (Ans += Que(1, 1, n, Ldfn[x] + 1, Ldfn[y]));
}

int fa[N], cnt;
struct QUERY {
    int opt, x, y, Ans;
} Q[N * 3];
struct QUESTION {
    int Find(int x) {
        return x == fa[x] ? x : fa[x] = Find(fa[x]);
    }
    bool Uni(int x, int y) {
        x = Find(x), y = Find(y);
        if (x != y) return fa[x] = y, --cnt, true;
        return false;
    }
    inline bool operator () (const EDGE &x, const EDGE &y) const {
        return x.to < y.to || (x.to == y.to && x.nex < y.nex);
    }
    void Work() {
        rep (i, 1, n) fa[i] = i;
        cnt = n;

        rep (i, 1, m) if (!mark[i]) if (Uni(total[i].to, total[i].nex)) {
            edgemark[i] = true;
            Addedge(total[i].to, total[i].nex);
            Addedge(total[i].nex, total[i].to);
        }

        Dfs1(1, 1); Dfs2(1, 1);
        Built(1, 1, n);

        rep (i, 1, m) if (!mark[i] && !edgemark[i])
            Update(total[i].to, total[i].nex);

        dep (i, q, 1) {
            if (Q[i].opt == 0) Update(Q[i].x, Q[i].y);
            else Q[i].Ans = Query(Q[i].x, Q[i].y);
        }

        rep (i, 1, q) if (Q[i].opt == 1) printf("%d\n", Q[i].Ans);
    }
} p100;

int main() {
    n = read<int>(), m = read<int>();
    rep (i, 1, m) {
        int x = read<int>(), y = read<int>();
        if (x > y) swap(x, y);
        total[i] = (EDGE) { x, y };
    }

    sort(total + 1, total + m + 1, QUESTION());

     int opt = read<int>();
     while (opt != -1) {
        int x = read<int>(), y = read<int>(), i = ++q;
        if (opt == 0) {
            if (x > y) swap(x, y);
            int k = lower_bound(total + 1, total + m + 1,
                                (EDGE) { x, y }, QUESTION()) - total;
            mark[k] = true;
            Q[i] = (QUERY) { opt, x, y, 0 };
        } else {
            Q[i] = (QUERY) { opt, x, y, 0 };
        }
        opt = read<int>();
    }

    p100.Work();
    return 0;
}