集合上的动态规划--最优配对问题
题目:刘汝佳《算法竞赛入门经典》,集合上的动态规划---最优配对问题 题意:空间里有n个点P0,P1,...,Pn-1,你的任务是把它们配成n/2对(n是偶数),使得每个点恰好在一个点对中。所有点对中两点的距离之和应尽量小。状态:d(i,S)表示把前i个点中,位于集合S中的元素两两配对的最小距离和 状态转移方程为:d(i,S)=min{|PiPj|+d(i-1,S-{i}-{j}}
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <math.h> 5 #include <algorithm> 6 using namespace std; 7 const int N = 20; 8 const int INF = 0x3f3f3f3f; 9 double d[N + 5][(1 << N) + 10]; 10 struct Node 11 { 12 int x, y, z; 13 }node[N + 10]; 14 double dist(int n1, int n2) 15 { 16 return sqrt( double( (node[n1].x - node[n2].x) * (node[n1].x - node[n2].x) + (node[n1].y - node[n2].y) * (node[n1].y - node[n2].y) + (node[n1].z - node[n2].z) * (node[n1].z - node[n2].z))); 17 } 18 int main() 19 { 20 int n; 21 scanf("%d", &n); 22 for(int i = 1; i <= n; i++) 23 { 24 scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].z); 25 } 26 for(int i = 0; i < n; i++) 27 { 28 for(int S = 0; S < (1 << (i + 1) ); S++) 29 { 30 if(S == 0) 31 d[i][S] = 0; 32 else 33 d[i][S] = INF; 34 if(S & (1 << i)) 35 { 36 for(int j = 0; j < i; j++) 37 { 38 if(S & (1 << j) ) 39 d[i][S] = min(d[i][S], dist(i, j) + d[i - 1][S ^ (1 << i) ^ (1 << j)]); 40 } 41 } 42 else if(i != 0) 43 { 44 d[i][S] = d[i - 1][S]; 45 } 46 } 47 } 48 printf("%.3lf\n", d[n - 1][(1 << n) - 1]); 49 return 0; 50 }
状态可以进行压缩,i的值其实隐藏在S中,S中最高位为1的即为i,所以需要一次查找,从n-1到0进行一次历编即可,整个运算下来,平均查找次数仅为2。而且方法二比方法一情况简单很多,也比较容易理解。
1 #include <cstring> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cmath> 5 #include <iostream> 6 using namespace std; 7 const int N = 20; 8 const int INF = 0x3f3f3f3f; 9 double d[(1 << N) + 10]; 10 struct Node 11 { 12 int x, y, z; 13 }node[N + 10]; 14 int n, S; 15 double dist(int n1, int n2) 16 { 17 return sqrt( double( (node[n1].x - node[n2].x) * (node[n1].x - node[n2].x) + (node[n1].y - node[n2].y) * (node[n1].y - node[n2].y) + (node[n1].z - node[n2].z) * (node[n1].z - node[n2].z))); 18 } 19 void input() 20 { 21 for(int i = 0; i < n; i++) 22 { 23 scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].z); 24 } 25 S = 1 << n; 26 d[0] = 0; 27 } 28 void solve() 29 { 30 for(int s = 1; s < S; s++) 31 { 32 int i, j; 33 d[s] = INF; 34 for(i = n - 1; i >= 0; i--) //d[s]就是前i个位于集合s中距离最小,只要找到i,再从i往前找j不就行了。 35 if(s & (1 << i)) 36 break; 37 for(j = i - 1; j >= 0; j--) 38 if(s & (1 << j)) 39 d[s] = min(d[s], dist(i, j) + d[s ^ (1 << i) ^ (1 << j)]); 40 } 41 } 42 int main() 43 { 44 scanf("%d", &n); 45 input(); 46 solve(); 47 printf("%0.3lf\n", d[S - 1]); 48 return 0; 49 }
这道题用递归实现更好一些,因为只需要判断n为偶数的情况,这就是递归运算的好处,而非递归则需要全部都进行一次运算。
1 #include <cstring> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cmath> 5 #include <iostream> 6 using namespace std; 7 const int N = 20; 8 const int INF = 0x3f3f3f3f; 9 double d[(1 << N) + 10]; 10 struct Node 11 { 12 int x, y, z; 13 }node[N + 10]; 14 int n, S; 15 double dist(int n1, int n2) 16 { 17 return sqrt( double( (node[n1].x - node[n2].x) * (node[n1].x - node[n2].x) + (node[n1].y - node[n2].y) * (node[n1].y - node[n2].y) + (node[n1].z - node[n2].z) * (node[n1].z - node[n2].z))); 18 } 19 void input() 20 { 21 scanf("%d", &n); 22 for(int i = 0; i < n; i++) 23 { 24 scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].z); 25 } 26 S = 1 << n; 27 //memset(d, INF, sizeof(d)); 慎用emset() 28 for(int i = 0; i < S; i++) 29 d[i] = INF; 30 d[0] = 0; 31 32 } 33 double dp(int s) 34 { 35 if(d[s] != INF) 36 return d[s]; 37 int i, j; 38 for(i = n - 1; i >= 0; i--) 39 if(s & (1 << i)) 40 break; 41 for(j = i - 1; j >= 0; j--) 42 if(s & (1 << j)) 43 d[s] = min(d[s], dp(s ^ (1 << i) ^ (1 << j)) + dist(i, j)); 44 return d[s]; 45 } 46 int main() 47 { 48 input(); 49 printf("%0.3lf\n", dp(S - 1)); 50 return 0; 51 }