1 模版介绍
拓扑排序+动态规划结合使用可以求有向无环图的最长路或最短路。
-
数据存储:
· din数组:存储各顶点入度数。
· q队列:让入度为0的点入队,进行拓扑排序。
· score数组:存储以顶点v为终点的最值。
· ans变量:存储最长路或最短路的值。
· G数组:邻接表(也可以用链式前向星)。
· W数组:与G数组下标对应,存储权值。 -
算法过程:
step 1:输入各边的起点、终点和权值。更新数组din、G和W。
step 2:如果求最长路,初始化score数组元素为一个较小的值(求最长路反之)。
step 3:通过判断din的值,让入度为0的点进入队列。
step 4:使用拓扑排序求最值:
1)获取队头元素存在ft中,队头元素出队。
2)用循环变量i遍历邻接表G[ft],令din[G[ft][i]]–。更新ans的值为min(ans,score[ft]+W[ft][i])。同时也要对score[G[ft][i]]做更新,score[G[ft][i]]=min(score[G[ft][i]],score[ft]+W[ft][i])。
3)判断如果G[ft][i]的入度为0,则该点入队
一直这样做,直到队列为空。
step 5:输出ans。 -
算法解释:
求最值我们常用动态规划,动态规划要有最优子结构,并且无后效性,为了做到这一点,我们可以用拓扑排序把先遍历到的节点最值确定再扩展到后续节点。
① 我们首先让入度为0的点入队,保证起点合法。
② 在扩展路径的过程中,我们通过遍历队头元素的邻接表,把所有可能的点都试一遍,如果发现当前节点最值加上邻接的点的值大于当前最值ans,则更新ans。同时,更新邻接的点的最值,以便后续用该点扩展的最值也是正确的。
③ 当一个点v的入边全部被遍历后,score[v]便得到了以v为终点的最值。我们这时把点v加入队列,继续扩展。
例题
例题1 洛谷P1807
解题思路(参考洛谷题解)
这道题属于模版题,但是要注意一下题目细节:
输入格式里面说了“每行 3 个整数 u,v,w(u<v),代表存在一条从 u 到 v 边权为 w 的边”,可以知道1号点是没有入边的,因为没有编号小于1。还有一个要注意的点是,可能有除了1号点外入度为0的点,而题目要我们求1,n间的最长路径,所以起点只能是1号点,其他入度为0的点无法抵达,我们要先用拓扑排序把这些点相邻的节点的度数减去。然后节点1入队,再用一次拓扑排序去求最值。
AC代码
#include<bits/stdc++.h>
#define maxn 1600
#define inf 0x3f3f3f3f
using namespace std;
long long ans=-inf,n,m,din[maxn],score[maxn];
queue<int> q;
vector<int> G[maxn],W[maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
G[u].push_back(v);
W[u].push_back(w);
din[v]++;
}
for(int i=2;i<=n;i++) {
score[i]=-inf;
if(din[i]==0)
q.push(i);
}
while(q.size()){
int ft=q.front();
q.pop();
for(int i=0;i<G[ft].size();i++){
if(--din[G[ft][i]]==0) q.push(G[ft][i]);
}
}
q.push(1);
while(q.size()){
int ft=q.front();
q.pop();
for(int i=0;i<G[ft].size();i++){
din[G[ft][i]]--;
score[G[ft][i]]=score[G[ft][i]]>score[ft]+W[ft][i]?score[G[ft][i]]:score[ft]+W[ft][i];
if(din[G[ft][i]]==0)
q.push(G[ft][i]);
}
}
if(score[n]==-inf) cout<<-1;
else cout<<score[n];
return 0;
}
例题2:洛谷P10166
解题思路(参考洛谷题解)
这道题加边思路是:1.找两个不相邻的点加两条边;2.找相邻的点加一条边。加两条边的话,我们最后就是找序列最小的两个顶点加边,最值存在ans中。后面再通过拓扑排序,去看看有没有相邻的节点加一条边代价小于ans,如果有则更新ans。
AC代码
#include<bits/stdc++.h>
#define maxn 1000005
#define ll long long
using namespace std;
struct edge{
ll u,v,nt;
}e[maxn];
queue<ll> q;
ll m,n,a[maxn],ans,dp[maxn],cnt,din[maxn],h[maxn];
void add(int a,int b){
++cnt;
e[cnt].u=a;
e[cnt].v=b;
e[cnt].nt=h[a];
h[a]=cnt;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
dp[i]=a[i];
}
ll x=0x3f3f3f3f,y=0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
if (a[i] <= x) y = x, x = a[i];
else if (a[i] < y) y = a[i];
}
ans = x + y << 1;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
add(u,v);
din[v]++;
}
for(int i=1;i<=n;i++){
if(din[i]==0) q.push(i);
}
while(q.size()){
ll ft=q.front();
q.pop();
for(int i=h[ft];i;i=e[i].nt){
int u=e[i].u,v=e[i].v;
din[v]--;
ans=ans<dp[u]+a[v]?ans:dp[u]+a[v];
dp[v]=dp[v]<dp[u]?dp[v]:dp[u];
if(din[v]==0) q.push(v);
}
}
for(int i=1;i<=n;i++){
if(din[i]) ans=0;
}
cout<<ans;
return 0;
}
例题3 洛谷P3387
解题思路(参考洛谷题解)
使用tarjan算法求强连通分量,之后把每个强连通分量当成一个点建图,使用拓扑排序+dp求解最值。
AC代码
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
int cnt, m, n, dfn[maxn], a[maxn], low[maxn], val_color[maxn], color[maxn], num_color;
int Din[maxn],mm[10005][10005],score[maxn],cc[maxn];
bool vis[maxn], vc[maxn];
stack<int> s;
vector<int> G[maxn],Dout[maxn];
queue<int> q;
void tarjan(int x) {
cnt++;
dfn[x] = low[x] = cnt;
vis[x] = 1;
s.push(x);
for (int i = 0; i < G[x].size(); i++) {
int q = G[x][i];
if (dfn[q] == 0) {
tarjan(q);
low[x] = min(low[x], low[q]);
} else if (vis[q])
low[x] = min(low[x], dfn[q]);
}
if (dfn[x] == low[x]) {
int t = s.top();
num_color++;
while (t != x) {
cc[num_color]++;
s.pop();
color[t] = num_color;
val_color[num_color] += a[t];
vis[t] = 0;
t = s.top();
}
s.pop();
color[x] = num_color;
cc[num_color]++;
val_color[num_color] += a[x];
vis[x] = 0;
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (dfn[i] == 0) {
tarjan(i);
}
}
for (int j = 1; j <= n; j++) {
for (int k = 0; k < G[j].size(); k++) {
if (color[j] != color[G[j][k]]&&mm[color[G[j][k]]][color[j]]==0){
Din[color[G[j][k]]]++;
Dout[color[j]].push_back(color[G[j][k]]);
mm[color[j]][color[G[j][k]]]=1;
mm[color[G[j][k]]][color[j]]=1;
}
}
}
int ans=0;
for (int i = 1; i <= num_color; i++) {
score[i]=val_color[i];
ans=max(ans,score[i]);
if(Din[i]==0) q.push(i);
}
while(q.size()){
int ft=q.front();
q.pop();
for(int i=0;i<Dout[ft].size();i++){
ans=max(ans,score[ft]+val_color[Dout[ft][i]]);
score[Dout[ft][i]]=max(score[Dout[ft][i]],score[ft]+val_color[Dout[ft][i]]);
Din[Dout[ft][i]]--;
if(Din[Dout[ft][i]]==0) q.push(Dout[ft][i]);
}
}
cout<<ans;
return 0;
}