Ural 1557 DFS 树

题意

传送门 Ural 1557 Network Attack

题解

已知一个无向连通图,求删除两条边后使图不连通的方案数。

考虑 D F S DFS DFS 树。若删除的边中至少有一条割边,则一定满足条件。之后仅考虑不包含割边的情况。若删除的两条边都是非树边,则无法使图不连通;若删除的边中仅有一条非树边,那么仅当这条非树边被唯一的非树边覆盖时,才能使图不连通;若删除的两条边都是树边,则应该满足两条边被同一组非树边覆盖,条件等价于这两条树边 ( u , v ) , ( x , y ) (u,v),(x,y) (u,v),(x,y) 被非树边覆盖的次数相同,且位置靠近叶子的边,假设为 ( u , v ) (u,v) (u,v),已其深度较大的节点 u u u 为根的子树中的返祖边所能到达的深度小于 u u u 的节点中,深度最大的节点的深度小于 x , y x,y x,y 中深度较小的节点(令其为 f ( u ) f(u) f(u))。

每一条边可以在其深度较大的节点处进行维护。割边可以在求 DFS 树时求出,树边被非树边覆盖的次数可以进行树上差分求解。 f ( u ) f(u) f(u) 可以在 DFS 中递归求解,具体而言,维护每一个节点连接的非树边中的返祖边(指向深度小于其的节点)所连接的节点的集合,在回溯至节点 u u u 时,通过时间戳 O ( n ) O(n) O(n) 遍历子树,将子孙节点所维护的集合中深度大于等于 u u u 的节点弹出,同时更新 f ( u ) f(u) f(u)

总时间复杂度 O ( n 2 + m ) O(n^2+m) O(n2+m)

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const int MAXN = 2E3 + 5;
struct edge
{
    int to, rev;
    bool tree;
};
int N, M, cut;
vector<edge> G[MAXN];
int dfn[MAXN], low[MAXN], f[MAXN], sum[MAXN];
int idx[MAXN], L[MAXN], R[MAXN];
vector<int> rec[MAXN];
ll res;

void _min(int &x, int y) { x = min(x, y); }

void _max(int &x, int y) { x = max(x, y); }

void add_edge(int u, int v)
{
    G[u].pb({v, (int)G[v].size(), 0});
    G[v].pb({u, (int)G[u].size() - 1, 0});
}

void dfs(int u, int &k)
{
    idx[k] = u, L[u] = k;
    dfn[u] = low[u] = k++;
    rec[u].clear();
    for (auto &e : G[u])
    {
        int v = e.to;
        if (dfn[v] == -1)
        {
            e.tree = G[v][e.rev].tree = 1;
            dfs(v, k);
            sum[u] += sum[v];
            _min(low[u], low[v]);
            if (low[v] > dfn[u])
                ++cut;
        }
        else if (!e.tree)
        {
            if (dfn[u] < dfn[v])
                continue;
            ++sum[u], --sum[v];
            _min(low[u], dfn[v]);
            rec[u].pb(dfn[v]);
        }
    }
    if (sum[u] == 1)
        ++res;
    R[u] = k;
    f[u] = -1;
    sort(rec[u].begin(), rec[u].end());
    for (int i = L[u]; i < R[u]; ++i)
    {
        int v = idx[i];
        while (rec[v].size() && rec[v].back() >= dfn[u])
            rec[v].pop_back();
        if (rec[v].size())
            _max(f[u], rec[v].back());
    }
    if (sum[u] == 0)
        return;
    for (int i = L[u] + 1; i < R[u]; ++i)
    {
        int v = idx[i];
        if (sum[v] == sum[u] && f[v] != -1 && f[v] < dfn[u])
            ++res;
    }
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    while (cin >> N >> M)
    {
        for (int i = 0; i < N; ++i)
            G[i].clear();
        res = cut = 0;
        for (int i = 0; i < M; ++i)
        {
            int u, v;
            cin >> u >> v;
            --u, --v;
            add_edge(u, v);
        }
        for (int i = 0; i < N; ++i)
            dfn[i] = -1, sum[i] = 0;
        int k = 0;
        dfs(0, k);
        ll a = M, b = M - cut;
        res += a * (a - 1) / 2 - b * (b - 1) / 2;
        cout << res << '\n';
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值