模拟赛 轰炸

题目大意

n n n 座城市,城市之间建立了 m m m 条有向的地下通道。

你需要发起若干轮轰炸,每轮可以轰炸任意多个城市。但每次轰炸的城市中,不能存在两个不同的城市 i i i j j j 满足可以通过地道从城市 i i i 到达城市 j j j

你需要求出最少需要多少轮可以对每座城市都进行至少一次轰炸。

数据范围

对于 20 % 20\% 20% 的数据, n , m ≤ 10 n,m\leq10 n,m10

对于 40 % 40\% 40% 的数据, n , m ≤ 1000 n,m\leq1000 n,m1000

对于另外 30 % 30\% 30% 的数据,保证无环。

对于 100 % 100\% 100% 的数据, n , m ≤ 1000000 n,m\leq1000000 n,m1000000

思路

首先我们先来看无环的情况。

对于每一条链,我们都需要花费链的长度次来将链上面的所有节点炸掉,所以最终答案就是所有链的长度的最大值。

对于如何求所有链的长度的最大值,可以考虑用拓扑加 DP 来求。

我们再来考虑环。

容易想到缩点,缩完点后的图是 DAG,也就是上面的情况,此时答案应改为链的权值和,每个点的权值为这个点代表了原图中的几个点。

缩点可以用 Tarjan 求强连通分量来做。

具体实现可以看代码。

代码

#include <bits/stdc++.h>
using namespace std;
int n, m, Ecnt, last[1000005], dfn[1000005], Low[1000005], Time, size[1000005];
int u[1000005], v[1000005], fa[1000005], f[1000005], ans, rd[1000005], bz[100005];
stack <int> st;
struct Edge { int to, next; } E[1000005];
void addedge(int u, int v) { Ecnt++, E[Ecnt].to = v, E[Ecnt].next = last[u], last[u] = Ecnt; }
void dfs(int x) {
    dfn[x] = Low[x] = ++Time, st.push(x);
    for (int xy = last[x]; xy; xy = E[xy].next)
        if (!dfn[E[xy].to])
            dfs(E[xy].to), Low[x] = min(Low[x], Low[E[xy].to]);
        else if (!bz[E[xy].to])
            Low[x] = min(Low[x], dfn[E[xy].to]);
    if (Low[x] == dfn[x]) {
        while (st.top() != x)
            size[x]++, fa[st.top()] = x, bz[st.top()] = 1, st.pop();
        st.pop(), bz[x] = 1;
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        fa[i] = i, size[i] = 1;
    for (int i = 1; i <= m; i++)
        scanf("%d%d", &u[i], &v[i]), addedge(u[i], v[i]), rd[v[i]]++;
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            dfs(i);
    Ecnt = 0;
    for (int i = 1; i <= n; i++)
        last[i] = rd[i] = 0, f[i] = size[i];
    for (int i = 1; i <= m; i++)
        if (fa[u[i]] != fa[v[i]])
            addedge(fa[u[i]], fa[v[i]]), rd[fa[v[i]]]++;
    while (!st.empty())
        st.pop();
    for (int i = 1; i <= n; i++)
        if (rd[i] == 0 && fa[i] == i)
            st.push(i);
    while (!st.empty()) {
        int x = st.top();
        st.pop();
        for (int xy = last[x]; xy; xy = E[xy].next) {
            rd[E[xy].to]--, f[E[xy].to] = max(f[E[xy].to], f[x] + size[E[xy].to]);
            if (rd[E[xy].to] == 0)
                rd[E[xy].to] = -1, st.push(E[xy].to);
        }
    }
    for (int i = 1; i <= n; i++)
        ans = max(ans, f[i]);
    printf("%d", ans);
    return 0;
}
  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值