【数塔DP|最长路】HDU-1224 Free DIY Tour

69 篇文章 0 订阅
14 篇文章 0 订阅

Free DIY Tour

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

Problem Description
Weiwei is a software engineer of ShiningSoft. He has just excellently fulfilled a software project with his fellow workers. His boss is so satisfied with their job that he decide to provide them a free tour around the world. It's a good chance to relax themselves. To most of them, it's the first time to go abroad so they decide to make a collective tour.

The tour company shows them a new kind of tour circuit - DIY circuit. Each circuit contains some cities which can be selected by tourists themselves. According to the company's statistic, each city has its own interesting point. For instance, Paris has its interesting point of 90, New York has its interesting point of 70, ect. Not any two cities in the world have straight flight so the tour company provide a map to tell its tourists whether they can got a straight flight between any two cities on the map. In order to fly back, the company has made it impossible to make a circle-flight on the half way, using the cities on the map. That is, they marked each city on the map with one number, a city with higher number has no straight flight to a city with lower number. 

Note: Weiwei always starts from Hangzhou(in this problem, we assume Hangzhou is always the first city and also the last city, so we mark Hangzhou both  1 and  N+1), and its interesting point is always 0.

Now as the leader of the team, Weiwei wants to make a tour as interesting as possible. If you were Weiwei, how did you DIY it?
 

Input
The input will contain several cases. The first line is an integer T which suggests the number of cases. Then T cases follows.
Each case will begin with an integer N(2 ≤ N ≤ 100) which is the number of cities on the map.
Then N integers follows, representing the interesting point list of the cities.
And then it is an integer M followed by M pairs of integers [Ai, Bi] (1 ≤ i ≤ M). Each pair of [Ai, Bi] indicates that a straight flight is available from City Ai to City Bi.
 

Output
For each case, your task is to output the maximal summation of interesting points Weiwei and his fellow workers can get through optimal DIYing and the optimal circuit. The format is as the sample. You may assume that there is only one optimal circuit. 

Output a blank line between two cases.
 

Sample Input
  
  
2 3 0 70 90 4 1 2 1 3 2 4 3 4 3 0 90 70 4 1 2 1 3 2 4 3 4
 

Sample Output
  
  
CASE 1# points : 90 circuit : 1->3->1 CASE 2# points : 90 circuit : 1->2->1
 
————————————————————————————————————————————————————————
前言:此题有两种思路。一个是最长路,另一个是DP。
思路:对于DAG(有向无环图)来说,求最长路并不是NP-Hard。因此本题可以用SPFA来做。
但是Dijkstra是绝对不行的。下面简要证明一下:
根据Dijkstra的性质,跑最短路的时候之所以正确,是因为直接和s相连的边当中,最短的那条边就一定是最短的s->a的路径。用它来松弛其它的边是有效的。
但是如果是最长路(设b、a都与s相连),s->b->a完全可以比s->a更长。因此无法得知哪条边可以用来松弛其它的边。
SPFA代码如下:
/**
 * ID: j.sure.1
 * PROG: 
 * LANG: C++
 */
/****************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <climits>
#include <iostream>
#define For(i,x,y) for(int i = x; i < y; i++)
#define For_(i, x, y) for(int i = x; i >= y; i--)
#define Mem(f,x) memset(f, x, sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Pri(x) printf("%d\n", x)
#define LL long long
using namespace std;
const int INF = 0x3f3f3f3f;
/****************************************/
const int N = 111, M = 11111;
struct Edge {
    int v, next;
    Edge(){}
    Edge(int _v, int _next):
        v(_v), next(_next){}
}e[M];
int head[N], tot, fa[N];
bool inq[N];
int  n, m, dis[N], val[N];

void __init__()
{
    tot = 0;
    Mem(head, -1);
    Mem(fa, -1);
    Mem(inq, 0);
    Mem(dis, 0);
}

void add(int u, int v)
{
    e[tot] = Edge(v, head[u]);
    head[u] = tot++;
}

void SPFA(int st)
{
    queue <int> q;
    q.push(st);
    inq[st] = 1;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        inq[u] = false;
        for(int i=head[u]; ~i; i=e[i].next) {
            int v = e[i].v;
            int tmp = dis[u] + val[v];
            if(dis[v] < tmp) {
                dis[v] = tmp;
                inq[v] = true;
                fa[v] = u;
                q.push(v);
            }
        }
    }
}

void PR(int cur)
{
    if(cur == 1) {
        printf("%d", 1);
        return ;
    }
    PR(fa[cur]);
    printf("->%d", cur == n+1 ? 1 : cur);
}

int main()
{
#ifdef J_Sure
    freopen("000.in", "r", stdin);
    freopen("999.out", "w", stdout);
#endif
    int T, kase = 0;
    Sca(T);
    while(T--) {
        if(kase) puts("");
        printf("CASE %d#\n", ++kase);
        Sca(n);
        For(i, 1, n+1) {
            Sca(val[i]);
        }
        val[n+1] = 0;
        Sca(m);
        __init__();
        while(m--) {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u, v);
        }
        SPFA(1);
        printf("points : %d\n", dis[n+1]);
        printf("circuit : ");
        PR(n+1);
        puts("");
    }
    return 0;
}

另外给出DP思路。其实DP的思路很简单。dp[ ]表示从起点到i点的最长路长度。(想到这一点,方程就好想了)
dp[i] = max{dp[1...i-1] + val[i]} (如果存在通路)
每一步计算dp[i]都是从之前所有计算过的dp[i]当中找最长的进行转移。
这样的话在计算dp[n+1]的时候,一定是从dp[1]~dp[n]当中找到最长的路。
复杂度其实还是O(n^2)。
如果思路再宽泛一些,可以将该题分类为数塔DP。
数塔DP思想:通过已知的最大值推理出未知的最大值。
对于仅有两个点的情况:1 -> 2,1 -> 2一定是1到2的最长路。
增加一个点。那么比较一下2 -> 3与1 -> 3哪条路更长,得到的一定是1 -> 3的最长路。
再增加,同理……
举例如下:求1 -> 6的最长路。

DP代码如下:
/**
 * ID: j.sure.1
 * PROG:
 * LANG: C++
 */
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <climits>
#include <iostream>
#define For(i, x, y) for(int i=x; i<y; i++)
#define For_(i, x, y) for(int i=x; i>=y; i--)
#define Mem(f, x) memset(f, x, sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Pri(x) printf("%d\n", x)
#define LL long long
using namespace std;
const int INF = 0x3f3f3f3f;
/****************************************/
const int N = 111;
int n, m, dp[N], val[N], fa[N];
bool mat[N][N];

void __init__()
{
    Mem(mat, 0);
    Mem(dp, 0);
    Mem(fa, -1);
}

void solve()
{
    For(i, 2, n+2) {
        For(j, 1, i) {
            //dp[i] = max(dp[i], dp[j] + val[i])
            if(mat[j][i] && d[i]<dp[j]+val[i]) {
                dp[i] = dp[j] + val[i];
                fa[i] = j;
            }
        }
    }
}

void PR(int cur)
{
    if(cur == 1) {
        printf("%d", 1);
        return ;
    }
    PR(fa[cur]);
    printf("->%d", cur == n+1 ? 1 : cur);
}

int main()
{
#ifdef J_Sure
    freopen("000.in", "r", stdin);
    freopen("999.out", "w", stdout);
#endif
    int T;
    Sca(T);
    For(kase, 0, T) {
        if(kase) puts("");
        printf("CASE %d#\n", kase+1);
        Sca(n);
        For(i, 1, n+1) Sca(val[i]);
        val[n+1] = 0;
        __init__();
        Sca(m);
        int u, v;
        For(i, 0, m) {
            scanf("%d%d", &u, &v);
            mat[u][v] = 1;
        }
        solve();
        fa[1] = -1;
        printf("points : %d\ncircuit : ", dp[n+1]);
        PR(n+1);
        puts("");
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值