状态压缩子集问题

描述:给定一个n(1≤n≤10)个数(可正可负)的集合,求一个划分方法,使得所有划分块的代价和最小。其中每个分块的代价和最小。其中每个块的代价为块内数字的和的平方。

分析:因为看到n最大为10,所以可以用状态压缩DP,复杂度最高为O(2^10*2^10)

设dp[i]表示状态为i的时候的最小代价和。

可以推出dp[X] = min(dp[Y] + dp[Z] | Y∪Z = X && Y∩Z = ∅}

初始的时候dp[X] = sum{a[i] | i在X集合中}^2.

所以可以写出

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 int n;
 8 #define INF 0x3f3f3f
 9 int a[11];
10 int dp[1030];
11 int main(){
12     while(cin>>n){
13         for(int i = 1; i <= n; i++){
14             scanf("%d", &a[i]);
15         }
16         for(int i = 0; i < (1<<10); i++){
17             int sum = 0;
18             int temp = i;
19             int co = 1;
20             while(temp!=0){
21                 int x = temp&1;
22                 if(x == 1){
23                     sum += a[co];
24                 }
25                 co++;
26                 temp = temp/2;
27             }
28             dp[i] = sum*sum;
29         }
30             
31         for(int i = 0; i < (1<<n); i++){
32             for(int j = 0; j < i; j++){
33                 if((i&j)==0){ //没有重合元素. 
34                     dp[i|j] = min(dp[i|j], dp[i]+dp[j]);            
35                 }
36             }
37         }
38         cout<<dp[(1<<n)-1]<<endl;
39         
40     }
41     
42     return 0;
43 }

但是可以进一步优化算法。

Z可以表示为Y^X,Y属于X可以表示(Y&X) = Y.

所以可以写为dp[X] = min(dp[Y] + dp[X^Y] | X&Y=Y])

枚举的时候优化为O(3^n的算法)

for(y = (x-1)&x; y > 0; y = (y-1)&x)

因为(x-1)&x表示将x的第一位1变为0.

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <string>
 5 #include <algorithm>
 6 using namespace std;
 7 int n;
 8 #define INF 0x3f3f3f
 9 int a[11];
10 int dp[1030];
11 int main(){
12     while(cin>>n){
13         for(int i = 1; i <= n; i++){
14             scanf("%d", &a[i]);
15         }
16         for(int i = 0; i < (1<<10); i++){
17             int sum = 0;
18             int temp = i;
19             int co = 1;
20             while(temp!=0){
21                 int x = temp&1;
22                 if(x == 1){
23                     sum += a[co];
24                 }
25                 co++;
26                 temp = temp/2;
27             }
28             dp[i] = sum*sum;
29         }
30             
31         for(int i = 0; i < (1<<n); i++){
32             for(int j = (i-1)&i; j>0; j = (j-1)&i){
33                 dp[i] = min(dp[i], dp[j]+dp[i^j]);            
34                 
35             }
36         }
37         cout<<dp[(1<<n)-1]<<endl;
38         
39     }
40     
41     return 0;
42 }

 

状态压缩 位运算的一些知识

左移操作:1<<X,表示把1向左移动X位。

      X<<1,表示把X的每一位向左移动1位。(看做乘2)右边不够的用0添上。

右移类似。

获取一个或多个固定位的值

X&(1<<j)表示获取X从右边数第j-1位的值。

把一个或多个固定位的位置置0。

x&(~(1<<j))

取反则用异或

x&(x-1)表示把x的第一个出现的1变成0.

 

posted @ 2015-03-18 17:43  下周LGD该赢了吧  阅读(167)  评论(0编辑  收藏  举报