Codeforces - 732F - Tourist Reform(Tarjan + 贪心)

题目链接:https://codeforces.com/problemset/problem/732/F

12.1 题意

给定一个 n ( 2 ≤ n ≤ 4 ⋅ 1 0 5 ) n(2 \le n \le 4 \cdot 10^5) n(2n4105) 个节点和 m ( 1 ≤ m ≤ 4 ⋅ 1 0 5 ) m(1 \le m \le 4 \cdot 10^5) m(1m4105) 条边的无向连通图,保证没有自环和重边。

现在要给每条边定一个方向,定义 r i r_i ri i i i 点在定向之后可以到达的点的数量。

问如何对每一条边进行定向,使得 min ⁡ i { r i } \min_i \left\{r_i \right\} mini{ri} 最大。

12.2 解题过程

我们先用 Tarjan 求出无向图中所有的桥,之后对于每一个边双连通分量进行 DFS,这样可以将每一个边双联通分量转化为强连通分量。

这样转化的目的是使每一个连通分量中的点相互可达,即一个强连通分量中点的 r i r_i ri 值至少为这个连通分量的点的个数。

现在我们只需确定所有的桥的方向。

做法很贪心,我们找到规模最大的强连通分量,其他的桥都指向它就可以保证答案最优。

实现时,只需从规模最大的强连通分量出发,进行 DFS,将经过的桥的反方向标记为答案即可。

时间复杂度: O ( n ) O(n) O(n)

12.3 代码

int n, m;
int head[maxn], cnt, pos;
int dfn[maxn], low[maxn];
int choice[2 * maxn], col[maxn], num[maxn], point[maxn];
bool bridge[2 * maxn], vis[maxn];
struct edge
{
    int u, v, nxt;
    int type;
} Edge[2 * maxn];
void init()
{
    memset(head, -1, sizeof(head));
    memset(choice, -1, sizeof(choice));
    cnt = 0, pos = 0;
}
void addedge(int u, int v, int type)
{
    Edge[cnt].u = u;
    Edge[cnt].type = type;
    Edge[cnt].v = v;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}
void tarjan(int id, int edg)
{
    dfn[id] = low[id] = ++pos;
    int flag = 0;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(!dfn[v])
        {
            tarjan(v, i);
            low[id] = min(low[id], low[v]);
            if(dfn[id] < low[v])    bridge[i] = bridge[i ^ 1] = true;
        }
        else if(i != (edg ^ 1)) low[id] = min(low[id], dfn[v]);
    }
}
void dfs(int id, int c)
{
    col[id] = c;
    num[c]++;
    if(!point[c])   point[c] = id;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(bridge[i])   continue;
        if(choice[i] == -1)
        {
            choice[i] = choice[i ^ 1] = Edge[i].type;
        }
        if(col[v] == c) continue;
        dfs(v, c);
    }
}
void dfs2(int id)
{
    vis[id] = true;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(vis[v])  continue;
        if(bridge[i] && choice[i] == -1)
        {
            choice[i] = choice[i ^ 1] = Edge[i ^ 1].type;
        }
        dfs2(v);
    }
}
int main()
{
    int u, v;
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &u, &v);
        addedge(u, v, 0);
        addedge(v, u, 1);
    }
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i]) tarjan(i, -1);
    }
    int tot = 0;
    for(int i = 1; i <= n; i++)
    {
        if(!col[i])
        {
            dfs(i, ++tot);
        }
    }
    int id = -1, maxvalue = 0;
    for(int i = 1; i <= tot; i++)
    {
        if(num[i] > maxvalue)
        {
            maxvalue = num[i];
            id = i;
        }
    }
    dfs2(point[id]);
    printf("%d\n", maxvalue);
    for(int i = 0; i < cnt; i += 2)
    {
        u = Edge[i].u;
        v = Edge[i].v;
        if(choice[i] == 0)  printf("%d %d\n", u, v);
        else printf("%d %d\n", v, u);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值