hdu3538

12 篇文章 0 订阅

A sample Hamilton path

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 306    Accepted Submission(s): 142


Problem Description
Give you a Graph,you have to start at the city with ID zero.
 

Input
The first line is n(1<=n<=21) m(0<=m<=3)
The next n line show you the graph, each line has n integers.
The jth integers means the length to city j.if the number is -1 means there is no way. If i==j the number must be -1.You can assume that the length will not larger than 10000
Next m lines,each line has two integers a,b (0<=a,b<n) means the path must visit city a first.
The input end with EOF.
 

Output
For each test case,output the shorest length of the hamilton path.
If you could not find a path, output -1
 

Sample Input
  
  
3 0 -1 2 4 -1 -1 2 1 3 -1 4 3 -1 2 -1 1 2 -1 2 1 4 3 -1 1 3 2 3 -1 1 3 0 1 2 3
 

Sample Output
  
  
4 5
Hint
I think that all of you know that a!=b and b!=0 =。=
 



题意:从编号为0的结点出发,输出一条最短的遍历所有的点(有且仅有一次)的路径,且部分点必须在某些点之前先遍历到。

做法:这属于集合dp。差不多是斯坦福的课件里面的例题了。dp[s][i]为点集合s里面以i为终点的哈密顿最优路径长度。然后再添加点拓展状态。详细看代码注释。

#include<algorithm>
#include <cstring>
#include<cstdio>
#include<stdlib.h>
#define L(i) (1 << (i))
#define MAXM 3000000
using namespace std;
int dp[3000000][22];
int dis[22][22];
int f[22];
int main()
{
   int n, m;
    while(~scanf("%d%d",&n, &m))
   {
        memset(f,0,sizeof(f));  //记录每个节点之前必须经历的节点
                                //的集合,如:1 3   2 3   4 3.f[3]的集合是1,2,4,
                                //利用状态压缩成一个数二进制形式为:10110
        for(int i = 0;i < L(n);i ++)//dp[s][i]状态定义为在集合s里面以点i为终点
            for(int j = 0;j < n;j ++)//的最优路径长度;置初值无穷大
                dp[i][j] = MAXM;    //这个值不能太大,否则会超内存。我分析是加到溢出非法访问数组了。。。。纯属猜测

         for(int i = 0;i < n;i ++)
            for(int j = 0;j < n;j ++)
                scanf("%d",dis[i] + j);

        for(int i = 0;i < m;i ++)
        {
            int u, v;
            scanf("%d%d", &u,&v);
            f[v] |= L(u);        //加入集合
        }

        dp[1][0] = 0;   //题目要求从ID为0的节点出发,所以只有这个才是合法的
        int S = L(n);
        for(int s = 0;s < S;s ++)           //枚举各种集合
            for(int i = 0;i < n;i ++)       //枚举每个集合里面的终点
                if(dp[s][i] == MAXM)continue;//如果状态合法才拓展
                else
                for(int j = 1;j < n;j ++)   //枚举新加入集合中的节点
                {
                    if(dis[i][j] == -1)continue; //如果新加入点与i点没有路径则不能加入
                    if(!(s & L(i)))continue;        //如果节点i不在集合s里面返回
                    if(s & L(j))continue;       //如果要加入节点在集合里面返回
                    if(f[j] != (s & f[j]))continue;//如果新加节点的前驱节点集合
                                                    //小于当前集合,说明还有前驱点为经历,
                                                    //还不能走这个点,返回
                    dp[s|L(j)][j] = min(dp[s][i] + dis[i][j],dp[s|L(j)][j]);
                }
        int ans = MAXM;
        S = L(n) - 1;
        for(int i = 0;i < n;i ++)	//i必须从0开始,因为有一个点的情况
            ans = min(ans, dp[S][i]);
        if(ans >= MAXM)printf("-1\n");
        else printf("%d\n",ans);
   }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值