原题: HDU 3362 http://acm.hdu.edu.cn/showproblem.php?pid=3362
开始准备贪心搞,结果发现太难了,一直都没做出来。后来才知道要用状压DP。
题意:题目给出n(n <= 18)个点的二维坐标,并说明某些点是被固定了的,其余则没固定,要求添加一些边,使得还没被固定的点变成固定的,可见题目中的图形sample。
由于n很小,而且固定点的顺序没有限制,所以需要用状态压缩DP。
注意:
1.当一个没固定的点和两个固定了的点连接后,该点就被(间接)固定了(三角形的稳定性质)
2.被固定的点还可以用来固定别的点
因此,对于当前需要固定的点,在已经是固定状态的点中选出两个距离当前点最小的,这就保证了局部最优,从起始状态开始转移,最后判断能否到达最终目标状态就可以了。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #define Mod 1000000007 using namespace std; #define N 100007 struct Point { double x,y; int f; }p[22]; double dp[1<<20]; int unfix[21]; double dis[20]; int n; double Dis(int i,int j) { Point ka = p[i]; Point kb = p[j]; return (double)sqrt((ka.x-kb.x)*(ka.x-kb.x)+(ka.y-kb.y)*(ka.y-kb.y)); } double Fixit(int state,int tofix) { int i = 0,j; int tmp = state; memset(dis,0,sizeof(dis)); while(i < n) { if(tmp & 1) //已固定点 { unfix[i] = 0; dis[i] = Dis(tofix,i); } else unfix[i] = 1; i++; tmp >>= 1; } double mini_1 = Mod; int tag = -1; for(i=0;i<n;i++) //选第一个点 { if(!unfix[i]) //fixed { if(dis[i] < mini_1) { mini_1 = dis[i]; tag = i; } } } double mini_2 = Mod; for(i=0;i<n;i++) //选第二个点 { if(i == tag) //选过 continue; if(!unfix[i]) { if(dis[i] < mini_2) mini_2 = dis[i]; } } if(mini_1+mini_2 >= Mod) return -1; return mini_1+mini_2; } int main() { int i,j; int Na; while(scanf("%d",&n)!=EOF && n) { int S = 0; Na = (1<<n)-1; for(i=0;i<n;i++) { scanf("%lf%lf%d",&p[i].x,&p[i].y,&p[i].f); if(p[i].f) S |= (1<<i); } for(i=0;i<=Na;i++) dp[i] = Mod; dp[S] = 0; for(i=S;i<Na;i++) { if(dp[i] == Mod) continue; for(j=0;j<n;j++) //选一个unfix的点固定 { if(i & (1<<j)) //已fix,跳过 continue; double delta = Fixit(i,j); if(delta == -1) continue; else //更新固定完这个点后的最少花费 dp[i|(1<<j)] = min(dp[i|(1<<j)],dp[i]+delta); } } if(dp[Na] == Mod) puts("No Solution"); else printf("%.6lf ",dp[Na]); } return 0; }