【题目链接】
洛谷 P3387 【模板】缩点
【题目考点】
1. 图论:强连通分量,有向图缩点
2. 图论:拓扑排序 有向无环图动规
【解题思路】
已知所有顶点点权是非负的,要想求出点权加和最大的路径。
如果该路径经过一个顶点u,那么必然应该经过顶点u所在强连通分量中的所有顶点,这样可以使得点权加和最大。
如果一条路径从顶点u进入u所在的强连通分量,从顶点v离开该强连通分量,但没有经过该强连通分量中的顶点x。由于允许多次经过一条边或者一个点,那么完全可以从顶点v,走回顶点x,再走回顶点v,这样多经过一个顶点x,点权加和一定变得更大或与之前相等。
因此可以将每个强连通分量可以看作一个顶点,只要经过该连通分量,就必然要经过该强连通分量中的所有顶点,增加的点权为该强连通分量中所有顶点的点权加和。
首先使用求强连通分量的算法(Kosaraju算法或Tarjan算法),求出有向图中的所有强连通分量
而后进行缩点,缩点后一定会得到一个有向无环图。
原图为e,设一个新图g,新图g中每个顶点相当于原图e中的一个强连通分量,新图g中每个顶点的权值是该顶点对应原图e中的强连通分量中所有顶点权值的和。如果原图e中顶点u到顶点v有一条有向边,同时u、v不属于同一个强连通分量,那么在新图g中,u所属强连通分量对应的顶点 到 v所属强连通分量对应的顶点有一条有向边。这样得到的新图g中可能有重边,不过重边不影响拓扑排序算法。
而后使用拓扑排序求有向无环图,也就是缩点后的g图中点权加和最大的路径,具体方法见该题:
ybt 1262:【例9.6】挖地雷
【题解代码】
解法1:Tarjan算法求强连通分量 缩点
#include<bits/stdc++.h>
using namespace std;
#define N 10005
int n, m, a[N], ga[N], dp[N], degIn[N], ans;
int dfn[N], low[N], ts, scc[N], sn;//scc[i]:顶点i所在的强连通分量 sn:强连通分量数量
vector<int> edge[N], g[N];
bool inStk[N];
stack<int> stk;
void tarjan(int u)
{dfn[u] = low[u] = ++ts;stk.push(u);inStk[u] = true;for(int v : edge[u]){if(dfn[v] == 0){tarjan(v);low[u] = min(low[u], low[v]);}else if(inStk[v])low[u] = min(low[u], dfn[v]);}if(dfn[u] == low[u]){int t;sn++;do{t = stk.top();scc[t] = sn;inStk[t] = false;stk.pop();} while(t != u);}
}
void topoSort()
{queue<int> que;for(int v = 1; v <= sn; ++v) if(degIn[v] == 0){ dp[v] = ga[v]; que.push(v);}while(!que.empty()){int u = que.front();que.pop();for(int v : g[u]){dp[v] = max(dp[v], dp[u]+ga[v]);if(--degIn[v] == 0)que.push(v);}}
}
int main()
{int u, v;cin >> n >> m;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 1; i <= m; ++i){cin >> u >> v;edge[u].push_back(v);}for(int i = 1; i <= n; ++i) if(dfn[i] == 0)tarjan(i);for(int u = 1; u <= n; ++u){ga[scc[u]] += a[u];for(int v : edge[u]) if(scc[u] != scc[v]){g[scc[u]].push_back(scc[v]);//可能会产生重边,不影响拓扑排序 degIn[scc[v]]++;//degIn[i]:连通分量i的入度}}topoSort();for(int v = 1; v <= sn; ++v)ans = max(ans, dp[v]);cout << ans;return 0;
}
解法2:Kosaraju算法求强连通分量 缩点
#include<bits/stdc++.h>
using namespace std;
#define N 10005
int n, m, a[N], ga[N], dp[N], degIn[N], ans;
int dfn[N], low[N], ts, scc[N], sn;//scc[i]:顶点i所在的强连通分量 sn:强连通分量数量
vector<int> edge[N], rg[N], g[N];
bool vis[N];
stack<int> stk;
void dfs_rg(int u)
{vis[u] = true;for(int v : rg[u]) if(!vis[v])dfs_rg(v);stk.push(u);
}
void dfs_g(int u)
{vis[u] = true;scc[u] = sn;for(int v : edge[u]) if(!vis[v])dfs_g(v);
}
void kosaraju()
{for(int u = 1; u <= n; ++u) if(!vis[u])dfs_rg(u);memset(vis, 0, sizeof(vis));while(!stk.empty()){int u = stk.top();stk.pop();if(!vis[u]){++sn;dfs_g(u);}}
}
void topoSort()
{queue<int> que;for(int v = 1; v <= sn; ++v) if(degIn[v] == 0){ dp[v] = ga[v]; que.push(v);}while(!que.empty()){int u = que.front();que.pop();for(int v : g[u]){dp[v] = max(dp[v], dp[u]+ga[v]);if(--degIn[v] == 0)que.push(v);}}
}
int main()
{int u, v;cin >> n >> m;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 1; i <= m; ++i){cin >> u >> v;edge[u].push_back(v);rg[v].push_back(u);}kosaraju();for(int u = 1; u <= n; ++u){ga[scc[u]] += a[u];for(int v : edge[u]) if(scc[u] != scc[v]){g[scc[u]].push_back(scc[v]);//可能会产生重边,不影响拓扑排序 degIn[scc[v]]++;//degIn[i]:连通分量i的入度}}topoSort();for(int v = 1; v <= sn; ++v)ans = max(ans, dp[v]);cout << ans;return 0;
}