状态压缩dp——蒙德里安的梦想、最短Hamilton路径(旅行商问题)

蒙德里安的梦想

在这里插入图片描述
当横向的小方格放好后,纵向小方格的位置是确定的,即总共数量等于横着摆放的小方格的方式的数量
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=12,M=1<<N;
long long dp[N][M];
//dp[i][j]表示第i列总方案数
//j表示第i列的状态,即第i-1列放入的小方块在第i列占用了哪些行,是一串二进制数,eg.01101
bool st[N];
//表示第i列的状态是否合法,当出现以下两种状态时不合法
//1.当第i列有连续奇数个0
//2.出现重叠即当i-1列状态为k时,k&j!=0
int main(){
    int n,m;
    while(cin>>n>>m,n||m){
       memset(dp,0,sizeof dp);//初始化
       //所有状态
       for(int i=0;i<1<<n;i++){
           st[i]=true;//假设当前列状态成立;
           int cnt=0;//表示当前状态连续0的个数
           //状态的每一位
           for(int j=0;j<n;j++){
               if(i>>j&1){//如果当前状态位是1,判断此时是否有奇数个0
                   if(cnt&1)st[i]=false;//第i个状态中存在奇数个0,是不合法的
                   cnt=0;
               }else{
                   cnt++;
               }
              
           } 
           if(cnt&1)st[i]=false;//判断最后一段中是否存在奇数个0
       }
       
       //dp过程
       //列数从0开始,第0列肯定不会被上一列占用位置,只能竖着放,dp[0][0]=1,只有一种方案数;
       dp[0][0]=1;
    
    
       //遍历每一列
       for(int i=1;i<=m;i++){
           //枚举所有状态
           for(int j=0;j<1<<n;j++){
               //枚举第i-1列所有状态
               for(int k=0;k<1<<n;k++){
                   if((j&k)==0&&st[j|k]){
                       dp[i][j]+=dp[i-1][k];
                   }
               }
           }
       }
       
       //第m-1列没有占用第m列时,为最终答案
       printf("%lld\n",dp[m][0]);
       
    }
    return 0;
}

最短Hamilton路径(旅行商问题)

在这里插入图片描述

旅行商问题是一个NP完全问题,没办法在一个多项式时间求出来
Hamilton路径:从0点到n-1点,不重不漏的经过每个点的路径
暴力做法:时间复杂度O((n-1)!),不得行

只需要考虑两个因素:
1.哪些点已经被用过(最右为0) 最多20个点 220
2.目前停在了哪个点上 20
总共的状态:220*20=2 *107

1.状态表示 dp[i][j]
i.集合 表示从0走到j,走过的点是i的所有路径
ii.属性 Min
1.状态计算
按倒数第二个点为k分
在这里插入图片描述

dp [state] [ j ]

state:
表示当前哪个点已被用过,用一个20位的二进制数表示,位i为1时表示该位存在(状态压缩

j:
表示当前停在了哪个点上

状态转移方程,假如从k转移过来
dp[state][j]=dp[state_k][k]+w[k][j]
(state_k = state去掉j之后的集合,state_k里要包含k)

时间复杂度=状态数量*状态转移时间复杂度
2 * 107*20=4 * 108

c++小贴士:
堆空间 静态变量,全局变量
栈空间 所有开在函数内部的空间(默认大小4M)
所以一般把大数组开到全局变量里

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=20,M=1<<20;
int w[N][N];
int dp[M][N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            scanf("%d",&w[i][j]);
        }
    }
    memset(dp,0x3f,sizeof dp);
    
    dp[1][0]=0;
    //在0点,状态机0位为1,当前位置为0,表示当前还没有走任何路程
    
    //遍历每一个状态
    for(int i=0;i<=1<<n;i++){
      for(int j=0;j<n;j++){
          //判断当前状态是否合法,即第j位是否为1
          if(i>>j&1){
              //枚举每个未走过的点
              for(int k=0;k<n;k++){
                  //判断state_k(即state-j)的第k位是否为1,减法优先级比右移运算大
                  if(i-(1<<j)>>k&1){
                     dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]);
                                         
                  }
              }           
          }
       }
    }
    
    
    printf("%d",dp[(1<<n)-1][n-1]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值