【模版】缩点


缩点


缩点可以简化图,去除图里的环,化环为点,简化题意。


算法流程:
①:各类数组的意义如下:
d f n [ ] dfn[] dfn[] 数组记录 该点是该点的访问时间。
l o w [ ] low[] low[]数组记录 该点所在的环上最早被访问到的点的时间。
s t a [ ] sta[] sta[] 统计该环上所有的点。
v i s [ ] vis[] vis[] 记录该点 是否在栈中。

②: t a r j a n tarjan tarjan d f s dfs dfs 遍历的名称。
每访问一个点,记录该点的 dfn 和 low 的信息,入栈 并 标记 vis 数组。
然后从该点继续遍历:
1.如果下一个点未被访问过,则访问该点并重复,比较 并记录 最小的 l o w low low 值。 (未被访问过的点的 l o w low low 值 不一定比当前点大, 因为即将访问的点 可能继续访问会出现环, l o w low low 值会记录较小值。)
2.如果下一个点被访问过,则 l o w low low 值记录 该环上最早被访问点的时间,需要取 m i n min min 来记录。

③:对于每个已经访问其所有出边的点,判断其 low 值 和 dfn 值是否相同。
1.如果相同,则说明该点是 所在环中的最早被访问的点(该环可以大小为1,自己和自己也看作一个环)。然后当前栈顶所在的点一直记录并弹出,直到弹出自身,期间所有的点一定在同一个环中。需要开一些其他数组,记录每个环内 所有点的归属 或是 其他信息。
2.如果不相同,则说明该点在环内,无事发生,找到 一个符合 上边 “1” 的点时再记录当前环。

④:枚举点及其出边,判断是否在同一个归属的环,不在则两个环连边。


例题:
[模版]缩点
间谍网络
最优贸易

例题代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <stack>
#include <ctime>
#include <queue>
#include <set>
using namespace std;
int n, m;
int head[202020],head1[202020],val[202020],val1[202020];
int dfn[202020],low[202020],sta[202020],col[202020],rec[202020];
bool vis[202020];
struct list
{
    int to,nxt;
}e[202020],e1[202020];
int read()
{
    int rt = 0, in = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
    return rt * in;
}

void add_old_edge(int u, int v)
{
    e1[++head1[0]].to = v;
    e1[head1[0]].nxt = head1[u];
    head1[u] = head1[0];
}
void add_edge(int u, int v)
{
    e[++head[0]].to = v;
    e[head[0]].nxt = head[u];
    head[u] = head[0];
}
void tarjan(int x)
{
    dfn[x] = low[x] = ++dfn[0];
    vis[x] = true;
    sta[++sta[0]] = x;
    for(int i = head1[x]; i; i = e1[i].nxt)
        if(!dfn[e1[i].to])
        {
            tarjan(e1[i].to);
            low[x] = min(low[x], low[e1[i].to]);
        }
        else if(vis[e1[i].to])
            low[x] = min(low[x], dfn[e1[i].to]);
    if(low[x] == dfn[x])
    {
        ++col[0];
        while(sta[sta[0]+1] != x)
        {
            col[sta[sta[0]]] = col[0];
            val[col[0]] += val1[sta[sta[0]]];
            vis[sta[sta[0]--]] = false;
        }
    }
}
int dfs(int x)
{
    if(rec[x])  return rec[x];
    rec[x] = val[x];
    int sum = 0;
    for(int i = head[x]; i; i = e[i].nxt)
    {
        if(!rec[e[i].to])   dfs(e[i].to);
        sum = max(sum, rec[e[i].to]);
    }
    rec[x] += sum;
    return rec[x];
}
int main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i++) val1[i] = read();
    for(int i = 1; i <= m; i++)
    {
        int u = read(), v = read();
        add_old_edge(u, v);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i);
    for(int i = 1; i <= n; i++)
        for(int j = head1[i]; j; j = e1[j].nxt)
            if(col[i] != col[e1[j].to])
                add_edge(col[i], col[e1[j].to]);
    int ans = 0;
    for(int i = 1; i <= col[0]; i++)
        if(!rec[i])
            ans = max(ans, dfs(i));
    printf("%d",ans);
    system("pause");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值