图论:P3387【模板】缩点 tarjan

P3387【模板】缩点
题目:

 

 题目思路:

  把一个有环图转换成无环图,就是利用tarjan算法,求出强连通分量,利用一个标记数组,将第n组强联通分量都标记为n,然后清空邻接矩阵,利用标记数组重新建立邻接关系,就是遍历每一个邻接关系,如果这两个点同属一个联通块就跳过,否则成为邻接关系。注意把点权压缩进缩的点,用一个二重循环,外层循环强联通分量的种数tot,内层遍历所有点,如果这个点被染成了外层循环i的数字i,sum[i]+=此点的点权,这样就缩边并且缩权值为一点了,或者在tarjan出栈的时候直接求和也可以,更简单。然后就求最大路径,利用DFS 树形DP求解。

 

一、缩点:

①tarjan:

 

void tarjan(int u)
{
    dfn[u]=low[u]=++num;
    s.push(u);
    for(int i=0;i<linjie[u].size();++i)
    {
        int v=linjie[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[v],low[u]);
        }
        else if(!vis[v])//v已经入栈 说明 u v已经构成环
        {
            low[u]=low[v];
        }
    }
    if(dfn[u]==low[u])
    {
        tot++;
        while(s.top()!=u)
        {
            liantong[s.top()]=tot;//把同属一联通块的染成一种颜色
            vis[s.top()]=1;
            s.pop();
        }
        liantong[u]=tot;
        vis[s.top()]=1;
        s.pop();
    }
}

 

②重新构建邻接关系:

1 memset(linjie,0,sizeof(linjie));//清空 重新构建邻接关系
2     for(int i=1;i<=m;++i)
3     {
4         if(liantong[a[i]]!=liantong[b[i]])
5         {
6             linjie[liantong[a[i]]].push_back(liantong[b[i]]);
7         }
8     }

③:把联通块的权值求和

 for(int i=1;i<=tot;++i)
    {
        for(int j=1;j<=n;++j)
        {
            if(liantong[j]==i)
            {
                sum[i]+=val[j];
            }
        }
    }

 

最后完整代码:

 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 1e5 + 5;
stack<int>s;
int dfn[maxn], low[maxn];
vector<int>linjie[maxn];
int a[maxn], b[maxn], val[maxn];//起点和终点数组 权值数组
int liantong[maxn];
bool vis[maxn];
int num, tot;
int n, m;
int f[maxn];//存储以i为起点搜索图的最大路径和
int sum[maxn];//起来联通块和
void tarjan(int u)
{
    dfn[u] = low[u] = ++num;
    s.push(u);
    for (int i = 0; i < linjie[u].size(); ++i)
    {
        int v = linjie[u][i];
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[v], low[u]);
        }
        else if (!vis[v])//v已经入栈 说明 u v已经构成环
        {
            low[u] = min(dfn[v],low[u]);
        }
    }
    if (dfn[u] == low[u])
    {
        tot++;
        while (s.top() != u)
        {
            liantong[s.top()] = tot;
            vis[s.top()] = 1;
            sum[tot] += val[s.top()];
            s.pop();
        }
        liantong[u] = tot;
        sum[tot] += val[s.top()];//直接算出联通块的权值和
        vis[s.top()] = 1;
        s.pop();
    }
}
void dfs(int x)
{
    if (f[x])return;//松弛过的点不需要再松弛
    f[x] = sum[x];
    int maxsum = 0;
    for (int i = 0; i < linjie[x].size(); ++i)
    {
        int v = linjie[x][i];
        if (!f[v])
            dfs(v);
        maxsum = max(maxsum, f[v]);
    }
    f[x] += maxsum;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
    {
        cin >> val[i];
    }
    for (int j = 1; j <= m; ++j)
    {
        cin >> a[j] >> b[j];
        linjie[a[j]].push_back(b[j]);
    }
    for (int i = 1; i <= n; ++i)
    {
        if (!dfn[i])
        {
            tarjan(i);
        }
    }
    memset(linjie, 0, sizeof(linjie));
    for (int i = 1; i <= m; ++i)
    {
        if (liantong[a[i]] != liantong[b[i]])
        {
            linjie[liantong[a[i]]].push_back(liantong[b[i]]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= tot; ++i)
    {
        if (!f[i])
        {
            dfs(i);
            ans = max(ans, f[i]);
        }
    }
    cout << ans;
    return 0;
}

 

 

 

 

 

 

 

posted @ 2022-05-08 10:24  朱朱成  阅读(39)  评论(0编辑  收藏  举报