• 状态压缩子集问题


    描述:给定一个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.

  • 相关阅读:
    CodeVs 1295 N皇后问题
    POJ 3349 Snowflake Snow Snowflakes
    链表API
    Hash API
    CodeVS 1220 数字三角形
    CodeVS 1045 回文数
    CodeVS 1058 合唱队形(DP--最长子序列问题)
    CodeVS 1018 单词接龙(DFS)
    关于图覆盖问题习题BY石家名
    软件测试作业(二)
  • 原文地址:https://www.cnblogs.com/titicia/p/4347910.html
Copyright © 2020-2023  润新知