hrbust/哈理工oj 2280 去吧,皮卡秋【状压dp】

去吧,皮卡秋
Time Limit: 1000 MSMemory Limit: 32768 K
Total Submit: 17(11 users)Total Accepted: 12(11 users)Rating: Special Judge: No
Description

“喏,解出来了。秘密是啥?”小果问。

小粒满脸欢喜,“小果你好棒啊!”

“秘密到底是啥?”

“秘密就是:本天才不会……”

=。=|||

小果听完这个秘密,啪……

小粒飞出了10米远。

小果看着楼下的雪堆,对小粒说:“你看,这下面有N个雪堆,你什么时候给每个雪堆都插上小红旗,我就扰了你”

小粒说:“大恩不言谢,我这就去”

“等等,你想先去哪个?”“这个吧”“送你一程。”啪……

已知小粒可以选择任意一个雪堆作为起点,现问小粒最少要跑多少距离,才能在每个雪堆上都插上小红旗?

Input

第一行一个整数表示测试组数t

每组第一行一个正整数N(1<=N<=15),表示点的个数

接下来n行,每行两个整数xi,yi(0<=xi,yi<=100),表示点的坐标

Output
求出连接n个点的最短路径长度。结果保留两位小数
Sample Input

3

3

0 0

0 1

0 2

3

0 0

0 1

1 0

4

0 0

1 0

2 0

1 1

Sample Output

2.00

2.00

3.41

Hint
红色样例中,小粒的一种可能走法为(0,0)->(1,0)->(2,0)->(1,1)
Source
"尚学堂杯"哈尔滨理工大学第五届程序设计团队赛(预选)

冬季校团队赛的一个热身赛的题,当时因为还是比较年轻,毕竟接触ACM也才不到四个月,带着最短路和最小生成树的思维一顿乱敲,怎么敲都是不过,也算是最近要学习状压dp,所以最近想起来这个题,做完了来写发题解。

思路:建设dp【i】【j】数组,表示i状态要终点是j的最短距离。构建思路是这样的:



要从i状态变成q状态,无非要从i状态中找一个点j,然后在q状态中找到一个点k,从j走到k。

辣么状态转移方程不难推出:
dp【q】【k】=min(dp【q】【k】,dp【i】【j】+dis【j】【k】)其中q一定是从i+(1 <<k)来的。

整个状态转移方程其实就是在表示从i状态中找一个点j,再选一个i状态中没有走过的点k,从j走到k。


辣么要从i状态变成q状态之前呢,要满足这样两个条件:


1、i状态里边有j这个点,才能把j这个点确定下来,辣么我们要怎样确定i状态有没有j这个点呢?根据位运算里边&运算符的操作规则:同1为1,其余为0的特性,我们就可以用

i&(1<<j)判断其值是否为0即可,如果为0,表示i状态中没有走过j(也就是i状态中没有j这个点),如果不为0,那么就表示有。

2、i状态里边没有k这个点,才能把k这个点确定下来,判断方法同上。

注意的点:位运算优先级很难记,不妨在有位运算的地方都加上括号。

AC代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<math.h>
using namespace std;
double dp[1<<17][17];
int x[20];
int y[20];
double dis(int i, int j)//计算距离
{
	return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&x[i],&y[i]);
        }
        memset(dp,0x7f,sizeof(dp));
        int end=1<<n;
        for(int i=0;i<n;i++)//初始化起点.
        {
            dp[1<<i][i]=0;
        }
        for(int i=0;i<end;i++)
        {
            for(int j=0;j<n;j++)
            {
                if (!(i&(1<<j))) continue;//如果i中没有j,辣么不合法
                for(int k=0;k<n;k++)
                {
                    if (i&(1<<k)) continue;//如果i中有j,辣么也不合法
                    int q=(i+(1<<k));
                    if(dis(j,k)+dp[i][j]<dp[q][k])
                    {
                        dp[q][k]=dis(j,k)+dp[i][j];//状态转移方程
                    }
                }
            }
        }
        double ans=1e300;//千万记住这里要开大值,我开小了wa了,看了别人代码才知道这块一定要大。
        for(int i=0;i<n;i++)
        {
            ans=min(ans,dp[end-1][i]);
        }
        printf("%.2lf\n",ans);
    }
}











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值