状态压缩DP入门题

原文地址:点击打开链接

 在n*n(n≤20)的方格棋盘上放置n 个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。仅供和我一样的菜鸟们参考

以n=4为例子解析源码

#include <iostream>
#include <cmath>
using namespace std;
__int64 a[1100000];
int main()
{
    __int64 n;
    while (cin >> n){
          memset(a, 0, sizeof(a));
          a[0] = 1;
          for (int i = 1; i <= 1<<n; i ++){//注意这里是1左移n位不是n<<1,显然这里是在枚举0000~1111的每一种状态 
              for (int j = i; j > 0; j -= (j&-j)){//首先注意这里是倒推,因为要由之前的状态推出现在的状态。 
                  a[i] += a[i&~(j&-j)];  //这里的位运算处理甚是漂亮,它可以保证每一次都刚好取到i的子集 ,首先j&-j可以得出在i之前的每一种状态j的最低位1的位置k,然后取反可以保证只有第k个位置刚好为0,那么求与之后就在原来i的基础上去除了第k个1 
              //比如说当前i枚举到0111,那么j&-j = 0001,则~(j&-j) = 1110,那么i&1110 = 0110,0110就是0111的一个子集,随后去掉当前最低位k,j变成0110,以此反复运算,直到j=0000 
              }    
          }
          cout<<a[(1<<n)-1]<<endl;      
    }  
    return 0;    
}

前一个状态压缩的升级版
在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。
和前面差别不大,主要是加了一个不可达点的限制,那么用二进制记录每一个不可达点然后还是按照以前的进行位运算,我写的代码不知道对不对,只核对了3以下的数据,希望路过神牛予以点评或修改
在n*n(n≤20)的方格棋盘上放置n 个车,某些格子不能放,求使它们不能互相攻击的方案总数。
和前面差别不大,主要是加了一个不可达点的限制,那么用二进制记录每一个不可达点然后还是按照以前的进行位运算,我写的代码不知道对不对,只核对了3以下的数据,希望路过神牛予以点评或修改

#include <iostream>
#include <cmath>
using namespace std;
__int64 a[1100000];
int is_allow[20];//用位记录所有不能放置棋子的位置 
int counter[21][21];
/*void turn(int x, int n)//转换成二进制输出,属测试代码 
{
     int t = x;
     int num= 0;
     int xx[100];
     while (x){
           xx[num++] = x%2;
           x/=2; 
     }     
     for (int i = num; i < n; i ++)cout<<0;
     for (int i = num-1; i >=0 ; i --)cout<<xx[i];
     
     cout<<endl;
}*/
void chang(int n)//将输入的地图转化到is_allow中 
{
     for (int i = 1; i <= n; i ++){
         int sum = 0;
         for (int j =n; j >= 1; j --){//转化成十进制存储到is_allow中 
             sum += counter[i][n-j+1]*(int)pow(2.0, n-j);
         }    
         is_allow[i] = sum;
     }         
}
int main()
{
    __int64 n;
    while (cin >> n){
          memset(is_allow, 0, sizeof(is_allow));
          memset(counter, 0, sizeof(counter));
          int t;
          scanf("%d", &t);
          for (int i = 0; i < t; i ++){
              int x, y;
              scanf("%d%d", &x, &y);
              counter[x][y] = 1;//记录不能放置棋子的位置 
          }
          chang(n);//转化 
          memset(a, 0, sizeof(a));
          a[0] = 1;
          for (int i = 1; i <= 1<<n; i ++){
              int cc = 0, tt = i;
              while (tt)tt&=(tt-1),cc ++;//cc记录当前放到了第几行 
              int tmps = i^is_allow[cc];//在i中去除不能放置的位置 
              for (int j = tmps; j > 0; j -= (j&-j)){
                  a[i] += a[i&~(j&-j)];
              }  
          }
          printf("%I64d", a[(1<<n)-1]);   
    }  
    return 0;    
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我们来看一个具体的例子。假设有一个长度为 n 的数组 A,其中每个元素都是 0 或 1,现在需要求出所有长度为 k 的子串中,元素为 1 的个数的最小值。 传统的动态规划方法需要使用二维数组来记录状态,时间复杂度为 O(nk),空间复杂度为 O(nk)。而使用状态压缩dp,我们可以将状态压缩为一个长度为 n 的二进制数 i,其中第 j 位为 1 表示 A[j] 在当前子串中出现了一次或多次,为 0 则表示没有出现。因此,我们只需要使用一个一维数组 f 来记录当前状态的最小值即可。 具体实现如下: ```python def min_ones_in_k_substrings(A, k): n = len(A) f = [float('inf')] * (1 << n) f[0] = 0 for i in range(n): for j in range(1 << i): if bin(j).count('1') == k: ones = bin(j & ((1 << i) - 1)).count('1') + A[i] f[j] = min(f[j], f[j & ~(1 << i)] + ones) return f[(1 << n) - 1] ``` 其中,f[i] 表示状态为 i 时的最小值,初始化为正无穷。在状态转移时,我们枚举当前状态的所有子集 j,如果 j 中的元素个数等于 k,则计算 j 中包含的所有元素为 1 的个数 ones,然后更新 f[j] 的值为 f[j] 和 f[j - {i}] + ones 中的较小值。其中,j - {i} 表示将 j 中的第 i 位(即 A[i] 对应的位置)置为 0。 最终,我们返回状态为全集时的最小值 f[(1 << n) - 1] 即可。由于状态总数为 2^n,因此时间复杂度为 O(n^22^n),空间复杂度为 O(2^n)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值