tyvj P1288 飘飘乎居士取能量块

P1288 飘飘乎居士取能量块
时间: 1000ms / 空间: 131072KiB / Java类名: Main
背景

9月21日,pink生日;9月22日,lina生日;9月23日,轮到到飘飘乎居士(狂欢吧,(^__^) 嘻嘻……)。
描述

9月21日,今天是pink的生日,飘飘乎居士当然要去别人的领土大闹一番啦!
为了收集更多的能量到pink家大闹,飘飘乎居士准备从后花园中取出自己多年积攒的p个能量块。后花园一共被划分n个地区,能量块被分散在里面,现在飘飘乎居士拿出地图,发现自己站在1的地方,而他要做的就是用最短的路程把所有的能量块取出,并且最后走到位于n的出口处,而飘飘乎居士一直是个懒人,他想知道最少要走多少路程才能够取到所有的能量块,并且走到出口
输入格式

第一行一个正整数n,表示花园被划分成了n个地区
接下来一个n*n的矩阵,代表个点之间的相互距离,数据保证从i走到i没有路程
在下来一个整数p,表示一共有p个能量块
接下来一行,表示各个能量块的位置,数据保证1和n没有能量块,且每个地区最多一个能量块
对于所有的数据 0 < n <=100 0 <=P<=10 任意两点的距离为一个小于1000的正整数
输出格式

一个数,飘飘乎居士所要行走的最小距离
测试样例1

输入

3
0 10 1
3 0 5
1 2 0
1
2
输出

7
备注

花园被分为3个地区,在2号地区有能量块,飘飘乎居士行走的路线如下
1->3->2->1->3
行走的总路程为7,也就是最后的答案。


【分析】
有技巧的图论。

先Floyed预处理出每两点的最短路径

然后DP。方程: f[i][k]表示从1到达i经过k集合中的点后到达n最小费用

这里的k集合指的是一串二进制。
例如:1001表示经过第一个点和第四个点。

当然这里要把离散的各个放着能量的点重新标号。
因为最多有十个能量点,所以是可行的

转移方程:
f[i][k]=min( f[j][get(k-j)] + g[j][i])
这里get()的意思是原先k集合拿掉j这个点后新的集合,j就是拿掉的点在图中的标号

答案就是 f[n][(1 << p)-1]


【代码】

//tyvj P1288 飘飘乎居士取能量块
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define M(a) memset(a,0,sizeof a)
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=100000000;
int f[105][5000],g[105][105],d[15];
int n,m,p;
inline int lowbit(int x) {return x&(-x);}
inline int dp(int point,int state)
{
    if(f[point][state]) return f[point][state];
    int i,tmp=state,ans=mxn;
    for(i=lowbit(tmp);tmp!=0;i=lowbit(tmp))
    {
        tmp-=i;
        int t=state-i;
        int k=d[(int)(log2(i))+1];
        ans=min(ans,dp(k,t)+g[k][point]);
    }
    return f[point][state]=ans;
}
int main()
{
    int i,j,k;
    memset(g,0x7f,sizeof g);
    scanf("%d",&n);
    fo(i,1,n)
      fo(j,1,n)
        scanf("%d",&g[i][j]);
    scanf("%d",&p);
    fo(i,1,p) scanf("%d",&d[i]);
    fo(k,1,n)
      fo(i,1,n)
        fo(j,1,n)
          g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
    fo(i,1,n) f[i][0]=g[1][i];
    printf("%d\n",dp(n,(1<<p)-1));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值