HDU3920

来源:互联网 发布:淘宝售假会怎么样 编辑:程序博客网 时间:2024/06/08 20:12

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 0