题意:给出一个有向无环图(DAG),可以不强连通,每个点有点权(正负)。
现在有从每个入度为0的点到它可达的出度为0的点的所有路径,求每条路径上所有点权之和的最大值。
分析:其实就是求一条最长路。
做法:
- 很容易想到将spfa的松弛过程改为更新最大值
- 记忆化搜索,取当前节点所有后继的最大值更新自己
- 拓扑排序,然后dp递推过去,是2的逆过程
分享:分享三份代码,与大家共同学习。
法1:反向建图,spfa变形。复杂度O(kE),k是一个不知道多大的常数(据说平均是2)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define LL long long
#define N 100001 //顶点数
#define M 1000001 //边数
//poj3249 DAG 最长路?
const LL INF = 1LL<<60;
struct edge {
int y, next;
};
int V, E; //顶点数,边数
LL d[N]; //最长距离
int q[M], head, tail; //队列,头,尾
int done[N]; //是否在队列里
edge e[M]; //E条边
int next[N], el; //i的下一条边的位置,记录边数
int val[N]; //节点值
int cnti[N]; //是否有入度
void add(int x, int y) { //加边
e[++el].y = y;
e[el].next = next[x];
next[x] = el;
}
void spfa() {
head = tail = 0;
rep1(i,V) d[i] = -INF, done[i] = 0, q[i] = 0;
rep1(s,V) if(cnti[s]==0) {
d[s] = val[s];
done[s] = 1;
q[tail++] = s;
}
while(head < tail) {
int x = q[head++];
done[x] = 0;
for(int i=next[x]; i; i=e[i].next) {
int y = e[i].y;
LL tmp = d[x]+(LL)val[y];
if(d[y] < tmp) { //松弛操作
d[y] = tmp;
if(!done[y]) { //不在队列,入队
q[tail++] = y;
done[y] = 1;
}
}
}
}
}
int main() {
while(scanf("%d%d", &V,&E) != EOF) {
el = 0;
rep1(i,V) next[i] = cnti[i] = 0;
rep1(i,V) scanf("%d", val+i);
for(int i=0, x,y; i<E; i++) {
scanf("%d%d", &x,&y);
add(y,x); //反向建图
cnti[x]=1;
}
spfa();
LL ans = -INF;
rep1(i,V) {
if(next[i]==0 && ans < d[i]) {
ans = d[i];
}
}
printf("%I64d\n", ans);
}
return 0;
}
法2:dfs记搜,复杂度O(V+E)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define LL long long
#define N 100001 //顶点数
#define M 1000001 //边数
//poj3249 DAG dfs
const LL INF = 1LL<<60;
struct edge{
int y, next;
};
int V, E; //顶点数,边数
LL d[N]; //最长距离
edge e[M]; //E条边
int next[N], el; //i的下一条边的位置,记录边数
int val[N]; //节点值
int cnti[N], cnto[N]; //入度,出度
inline void add(int x, int y) { //加边
e[++el].y = y;
e[el].next = next[x];
next[x] = el;
}
LL dfs(int v) {
if(d[v]!=-INF) return d[v];
if(cnto[v]==0) return d[v] = val[v];
for(int i=next[v]; i; i=e[i].next) {
d[v] = max(d[v], (LL)val[v]+dfs(e[i].y));
}
return d[v];
}
int main() {
while(scanf("%d%d", &V,&E) != EOF) {
el = 0;
rep1(i,V) next[i] = cnti[i] = cnto[i] = 0;
rep1(i,V) scanf("%d", val+i);
for(int i=0, x,y; i<E; i++) {
scanf("%d%d", &x,&y);
add(x,y);
cnti[y]++;
cnto[x]++;
}
LL ans = -INF;
rep1(i,V) d[i] = -INF;
rep1(i,V) {
if(cnti[i]==0) {
ans = max(ans, dfs(i));
}
}
printf("%I64d\n", ans);
}
return 0;
}
法3:拓扑排序+dp,复杂度O(V+E)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define rep1(i,n) for(int i=1;i<=n;i++)
#define LL long long
#define N 100001 //顶点数
#define M 1000001 //边数
//poj3249 spfa + dp
const LL INF = 1000000000000000; //1e15
struct edge {
int y, next;
};
int V, E; //顶点数,边数
LL d[N]; //最短距离
int q[M], head, tail; //队列,头,尾(切记:手写数组必须开到 > M)
edge e[M]; //E条边
int next[N], el; //i的下一条边的位置,记录边数
int val[N]; //节点权值
int ind[N], outd[N]; //入度,出度
void init() {
el = 0;
rep1(i,V) next[i] = ind[i] = outd[i] = 0;
rep1(i,V) d[i] = -INF;
}
inline void add(int x, int y) { //加边
e[++el].y = y;
e[el].next = next[x];
next[x] = el;
}
void tpo() { //拓扑排序
head = tail = 0;
rep1(i,V) if(ind[i]==0) {
d[i] = val[i];
q[tail++] = i;
}
while(head < tail) {
int x = q[head++];
for(int i=next[x]; i; i=e[i].next) {
int y = e[i].y;
d[y] = max(d[y], d[x]+val[y]);
ind[y]--;
if(ind[y]==0) q[tail++] = y;
}
}
}
int main() {
while(scanf("%d%d", &V,&E) != EOF) {
init();
rep1(i,V) scanf("%d", val+i);
for(int i=0, x,y; i<E; i++) {
scanf("%d%d", &x,&y);
add(x,y);
ind[y]++;
outd[x]++;
}
tpo();
LL ans = -INF;
rep1(i,V) if(outd[i]==0) ans = max(ans, d[i]);
printf("%I64d\n", ans);
}
return 0;
}