HDU 3920 Clear All of Them I(状压DP)题解
题意:2n个点,一个起点,开n枪,每枪必须打两个点,花费为起点到其中一点距离加上两点距离。问打完2n个点的最小花费。
思路:很显然应该dp状态,然后枚举i j两个空位置去填,那么复杂度$O(20 * 20 * n^{20})$,这个会超时。因为内存限制不能预处理每个状态的子状态。所以我们要想办法减少复杂度。显然复杂度多在重复枚举的过程,因为花费之和与枚举的顺序无关,即我用(a,b)(c,d)还是(c,d)(a,b)都是一种结果,那么我们不妨每次都让最小的先加进来。这样复杂度降为$O(20 * n^{20})$
代码:
#include<set> #include<map> #include<cmath> #include<queue> #include<cstdio> #include<vector> #include<cstring> #include <iostream> #include<algorithm> using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 10 + 5; const int M = maxn * 30; const ull seed = 131; const int INF = 0x3f3f3f3f; const int MOD = 1e4 + 7; double dp[1 << 20], w[1 << 20]; struct node{ double x, y; }p[25]; double dis(node a, node b){ return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } int main(){ // for(int i = 0; i < (1 << 20); i++){ // for(int j = 0; j < 20; j++){ // if((1 << j) & i) continue; // for(int k = j + 1; k < 20; k++){ // if((1 << k) & i) continue; // nex[i].push_back((1 << j) | (1 << k)); // } // } // } int T, ca = 1; scanf("%d", &T); while(T--){ double x, y; int n; scanf("%lf%lf", &x, &y); scanf("%d", &n); for(int i = 0; i < 2 * n; i++){ scanf("%lf%lf", &p[i].x, &p[i].y); } p[2 * n].x = x, p[2 * n].y = y; for(int i = 0; i < 2 * n; i++){ for(int j = i + 1; j < 2 * n; j++){ w[(1 << i) | (1 << j)] = min(double(INF), dis(p[i], p[j]) + dis(p[2 * n], p[i])); w[(1 << i) | (1 << j)] = min(w[(1 << i) | (1 << j)], dis(p[i], p[j]) + dis(p[2 * n], p[j])); } } for(int i = 0; i < (1 << (2 * n)); i++) dp[i] = INF; dp[0] = 0; for(int i = 0; i < (1 << (2 * n)); i++){ if(dp[i] == INF * 1.0) continue; int j; for(j = 0; j < 2 * n; j++){ if(!((1 << j) & i)) break; } for(int k = j + 1; k < 2 * n; k++){ if((1 << k) & i) continue; dp[i | (1 << j) | (1 << k)] = min(dp[i | (1 << j) | (1 << k)], dp[i] + w[(1 << j) | (1 << k)]); } } printf("Case #%d: %.2f\n", ca++, dp[(1 << (2 * n)) - 1]); } return 0; }