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).这里不再是随便选两个i和j了.因为集合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;
}