【题目链接】
洛谷 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;
}