状态压缩子集问题
描述:给定一个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.