POJ3229

题意:给出一个n(n<=15)个节点的带权无向图,边权为从经过此边的时间,点权为在此点停留的时间。给出m个点(m<=n),这些点必须经过。起点为1号点,且终点必须回到1号点。给出T,求在T时间内满足题意的路径上经过 (且停留)的最多的点的数目。
分析:这是一道图上的状压DP。已经游览的每个点用1表示,未游览的用0表示。某时刻的游览状态就可以用一个整数的二进制形式表示,这是采用状压的原因。定义状态f(i,j)为走到第i个点时,走过的点的状态为j时花费时间的量。则f(i,j)=min(f(i,j),f(k,j-(1<<i))+dis(k,i)+rest(i))。解释:i,k为j状态路径上的点,f(k,j-(1<<i))+dis(k,i)+rest(i)表示走到k点,j的上一个状态下的时间+k走到i的时间+在i休息的时间。枚举k不断更新i可以得到最优的f(i,j),枚举i,j可以出解。
    注意:国人装B用英语出题,坑死我了。第一,经过一个点,不一定要在这停,当然这个点就不被算进ans;第二,题目叙述有个地方有问题,用红色的改正了;第三,这个人一天花12小时旅游,但是如果他要花费20小时在路上,那么这要花费他一天多旅游的时间,如果他欣赏风景到一半当天的12小时结束了他明天会继续欣赏(卧槽你睡觉的时候不会赶车啊)。
    注意:状压节约内存,所以要从0编号,他给的节点序号全部-1;还有里面有除法,除不尽,用double存。
    下面给出代码
#include<algorithm>
#include<cstdio>
using namespace std;
const double inf = 1e9;
int n, m, d, st = 0,ans = -1,u,v,dis,op;
double f[16][1<<16],a[16],map[16][16];
//a存点权,map存边权
int main()
{
    while(scanf("%d%d%d",&n,&m,&d) &&(n||m||d) )
    {
        int temp;
        double day = d * 12.0;
        st = 0,ans = -1; //st表示满足题意的状态
        for(int i = 0; i < n; ++i) //初始化
        {
            for(int j = 0; j < 1<<n; ++j)
                f[i][j]=inf;
            for(int j = 0; j < n; ++j)
                map[i][j]=inf;
            map[i][i]=0.0;
        }
        for(int i = 0; i < m; ++i)
        {
            scanf("%d",&temp) ;
            st += 1<<(temp-1) ; //注意代码实现 注意代码风格
        }
        for(int i = 0; i < n; ++i) scanf("%lf", &a[i]) ;
        double t;
        while(scanf("%d%d%d%d",&u,&v,&dis,&op) &&(u||v||dis||op))
        {
            u--,v--; //节点号-1
            t = dis*1.0;
            if(op) t /= 120.0;
            else t /= 80.0;
            map[u][v] = map[v][u] = min(map[u][v], min(map[v][u], t)) ; //注意要有这句话,从a到b可能bus和train都通
        }
        for(int k = 0; k < n; ++k)
        for(int i = 0; i < n; ++i)
            if(i != k)
                for(int j = 0; j < n; ++j)
                    if(j != k)
                        map[i][j] = min(map[i][j],map[i][k]+map[k][j]) ;
        for(int i = 1; i < n; ++i)  //初始化1的邻接点的f值
            f[i][1<<i]=map[i][0]+a[i];
        for(int j = 0; j < 1<<n; ++j)  //dp
            for(int i = 0; i < n; ++i)
            {
                if(j&(1<<i) &&(1<<i) !=j) //dp的边界条件
                    for(int k = 0; k < n; ++k) if(j&(1<<k) &&(1<<k) !=j&&i != k)  //dp的边界条件
                            f[i][j]=min(f[i][j],f[k][j-(1<<i) ]+map[k][i]+a[i]) ;
                if((j&st) == st && f[i][j]+map[i][0] <= day) //如果满足题意
                {
                    int temp = j,cnt = 0;
                    while(temp) //统计游览了几个景点
                    {
                        if(temp&1) cnt++;
                        temp >>= 1;
                    }
                    ans = max(ans, cnt) ;
                }
            }
        if(ans >= 0) printf("%d\n",ans) ;
        else printf("No Solution\n") ;
    }
}


转载于:https://www.cnblogs.com/geng4512/p/5296951.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值