HDU3920

HDU3920 Clear All of Them I

现在你在一个大地图上,给出你所在的坐标(x,y),然后给出2*n个敌人的坐标,要你消灭这2*n个敌人.你手上有一把激光枪,每次射击你都可以任意选择两个目标,激光先从你的位置到第一个目标,然后再从第一个目标到第二个目标的位置,这样就消灭了这两个目标,激光枪消耗的能量是它总共走过的距离.

你可以选择先攻击哪个目标.

你的位置不可以变.

每个目标只能给攻击一次,所以你需要打n枪.

每个目标的位置唯一.

现在要求你消灭这2*n个目标所需要的最小能量值.

输入:首先是一个T ( T <= 100 ),表示实例个数.对于每个实例,第一行是两个整数(x,y)表示你的位置,第二行是一个n(1 <= n <= 10),表示这里有2*n个敌人,接下来2*n行是每行两个整数,表示敌人的坐标.所有整数都是在[-1000,1000]范围内.

输出:以Case #i: 6.00格式输出最小能量,保留两位小数.

分析:令d[S]表示消灭完了集合S中的敌人(成对出现)后,所需要的最小能量.

       d[S+{i,j}]= min{ d[S]+min_value(i,j) } min_value(I,j)表示消灭i和j所需要的最少能量.

复杂度分析:n<=10,d[S]的个数为100w,然后每次选i和j需要400,共100个实例,则就是400亿计算次.

这么做显然超时.

结果正确但超时的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
doubled[1<<22];
boolvis[1<<22];
struct point
{
    double x,y;
};
point man;
point enemy[20];
int n;
double length[25][25];
bool vis1[25][25];
double dist(inti,int j)//返回从起点到i,j的最短距离
{
    if(vis1[i][j])return length[i][j];
    point a = enemy[i], b = enemy[j],c=man;
    double len = sqrt( (a.x-b.x)*(a.x-b.x) +(a.y-b.y)*(a.y-b.y) );//i与j的距离
    double len1 = sqrt( (a.x-c.x)*(a.x-c.x) +(a.y-c.y)*(a.y-c.y) );//起点与i的距离
    double len2 = sqrt( (b.x-c.x)*(b.x-c.x) +(b.y-c.y)*(b.y-c.y) );//起点与j的距离
    length[i][j] = length[j][i] = min( len+len1, len+len2 );
    vis1[i][j]=vis1[j][i] =true;
    return length[i][j];
}
int main()
{
    int T;
   while(scanf("%d",&T)==1&&T)
    {
        for(int kase = 1;kase<=T;kase++)
        {
            scanf("%lf%lf",&man.x,&man.y);
            scanf("%d",&n);
            for(int i=0;i<2*n;i++)
                scanf("%lf%lf",&enemy[i].x,&enemy[i].y);
            memset(vis1,0,sizeof(vis1));
            memset(vis,0,sizeof(vis));
            vis[0]=true;
            d[0]=0.0;//初始为0
            for(int S=0;S<(1<<(2*n));S++)if(vis[S])//集合S有效
            {
                for(int i=0;i<2*n;i++)if( !(S&(1<<i) ) )//S中不包含i
                {
          /*可优化*/for(int j=i+1;j<2*n;j++)if( !( S&(1<<j) ) )//S中不包含j
                    {
                       if(!vis[S|(1<<i)|(1<<j)] )
                           d[S|(1<<i)|(1<<j)] = d[S]+dist(i,j);
                        else
                           d[S|(1<<i)|(1<<j)] = min( d[S|(1<<i)|(1<<j)] ,d[S]+dist(i,j) );
                       vis[S|(1<<i)|(1<<j)] = true;
                    }
                }
            }
            printf("Case #%d:%.2lf\n",kase,d[(1<<(2*n))-1]);
        }
    }
    return 0;
}

现在换一种解法.用记忆化搜索来算(可以去除很多无用的状态),然后用状态转移方程:

d[S] = min{ d[S-{i,j}]+min_value(i,j) } min_value(i,j)注意i是S中的最低位1,而j是S中的任意一个(但j>i).这里不再是随便选两个ij.因为集合S你无论如何其中的最低位i都是要和一个j进行配对了,而且(i,j)在第几被射击是没影响的,所以如果d[S]的最小值确实是由d[S-{i,j}]d[{i,j}]构成的话,那么先把(i,j)分离出来,再计算d[S-{i,j}]同样是可以得到最小值的.

AC代码:203ms

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
double d[1<<22];
bool vis[1<<22];
struct point
{
    double x,y;
};
point man;
point enemy[20];
int n;
double length[25][25];
bool vis1[25][25];
map<int ,int > m;
double dist(int i,int j)//返回从 起点到i再到j 或 起点到j再到i 的最短距离
{
    if(vis1[i][j])return length[i][j];//记忆化
    point a = enemy[i], b = enemy[j],c=man;
    double len = sqrt( (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) );//i与j的距离
    double len1 = sqrt( (a.x-c.x)*(a.x-c.x) + (a.y-c.y)*(a.y-c.y) );//起点与i的距离
    double len2 = sqrt( (b.x-c.x)*(b.x-c.x) + (b.y-c.y)*(b.y-c.y) );//起点与j的距离
    length[i][j] = length[j][i] = min( len+len1 , len+len2 );
    vis1[i][j]=vis1[j][i] =true;
    return length[i][j];
}
double dp(int S)//记忆化搜索DP
{
    if(vis[S])return d[S];
    vis[S]=true;
    double &ans = d[S];
    ans=-1.0;
    int lowbit = S&(-S);//S二进制形式最低位的i对应的值
    int i = m[lowbit];
    for(int j=i+1;j<2*n;j++)if(S&(1<<j))//j在S中,且j的位比i的位高
    {
        if(ans<0) ans = dp(S^(lowbit)^(1<<j) ) + dist(i,j) ;
        else  ans = min(ans, dp(S^(lowbit)^(1<<j)) + dist(i,j) );
    }
    return ans;
}
int main()
{
    int temp=1;
    for(int i=0;i<=20;i++)
    {
        m[temp]=i;
        temp *=2;
    }
    int T;
    while(scanf("%d",&T)==1&&T)
    {
        for(int kase = 1;kase<=T;kase++)
        {
            scanf("%lf %lf",&man.x,&man.y);
            scanf("%d",&n);
            for(int i=0;i<2*n;i++)
                scanf("%lf %lf",&enemy[i].x,&enemy[i].y);
            memset(vis1,0,sizeof(vis1));//用于标记dist[i][j]的
            memset(vis,0,sizeof(vis));//用于标记dp的
            vis[0]=true;
            d[0]=0.0;//初始为0

            printf("Case #%d: %.2lf\n",kase,dp( (1<<(2*n))-1 ) );
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值