poj 3249 Test for Job 最长路

题意:给出一个有向无环图(DAG),可以不强连通,每个点有点权(正负)。

            现在有从每个入度为0的点到它可达的出度为0的点的所有路径,求每条路径上所有点权之和的最大值。

分析:其实就是求一条最长路。

做法:

  1. 很容易想到将spfa的松弛过程改为更新最大值
  2. 记忆化搜索,取当前节点所有后继的最大值更新自己
  3. 拓扑排序,然后dp递推过去,是2的逆过程
坑点:数据量很大是很大,给了5秒。但主要是有卡的数据,方法1弄不好就会TLE。在此题的Discuss里面, boys指出了“最坏的情况下,n次spfa,也就是说O(n^2),绝对超时。例如:10000个点(入度为0)都指向一条长度为90000的链的首端,spfa做,需要9*10^8次。” 可以仔细琢磨一下。因为spfa太不稳定,所以会出现各种状况。由此,我们可以考虑反向建图,飘过这样的数据。要是说反过来还有这样的数据呢,那这种方法就放弃吧…

分享:分享三份代码,与大家共同学习。

法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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值