状态压缩DP做题笔记(一)

本文通过两道典型题目详细解析状态压缩动态规划的应用方法,包括如何使用位运算进行状态表示及状态转移,适用于有一定DP基础的学习者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

状态压缩DP做题笔记(一)

POJ1321

中文题目,这里就不再解释了

以前DFS做过,这次也先顺手写了个DFS的

然后DE了1小时BUG

#include <iostream>
#include <cstring>
#define N 10100
#define INF 0x3f3f3f3f
#define LL long long
#define eps 1e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
int n,ans;
char mp[9][9];
bool vis[9];
void dfs(int cur,int row,int num);
int main()
{
    ios::sync_with_stdio(false);
    int k;
    while(cin>>n>>k&&n!=-1){
        ans=0;
        mem(vis,0);
        for(int i=0;i<n;++i){
            for(int j=0;j<n;++j){
                cin>>mp[i][j];
            }
        }
        dfs(0,0,k);
        cout<<ans<<endl;
    }
    return 0;
}
void dfs(int cur,int row,int num)
{
    if(cur==num){
        ++ans;
        return;
    }
    for(int i=row;i<n-num+cur+1;++i){
        for(int j=0;j<n;++j){
            if(mp[i][j]=='#'&&!vis[j]){
                vis[j]=true;
                dfs(cur+1,i+1,num);
                vis[j]=false;
            }
        }
    }
}

然后这题的状态压缩是怎么想的呢

首先正如我们DFS直接用了一个vis[n]来记录被用掉的列一样,最后的,我们记录状态的时候所用dp[i][j]中的j的二进制表示的就是取了哪些列,状态转移方程为

最后数一遍状态为所需棋子个数的状态数就好了

#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
#include <vector>
#include <queue>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <bitset>
#define INF 0x3f3f3f3f
#define ULL unsigned long long
#define LL long long
#define N 10100
#define eps 10e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
const double PI=acos(-1.0);
int n,k;
char mp[10][10];
int legal[1000];
int d[10][1<<10];
void cal(int sta){
    bitset<10> a(sta);
    legal[sta]=a.count();
}
int main()
{
    ios::sync_with_stdio(false);
    for(int i=0;i<(1<<8);++i){
        cal(i);
    }
    while(cin>>n>>k&&n!=-1){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                cin>>mp[i][j];
            }
        }
        mem(d,0);
        d[0][0]=1;//不放子就能达到该状态,不能忘
        int sta=1<<n,news;
        for(int i=1;i<=n;++i){
            for(int j=0;j<sta;++j){
                d[i][j]+=d[i-1][j];//不放棋子就能达到该状态
                for(int u=1;u<=n;++u){
                    if(mp[i][u]=='#'&&((j&(1<<(u-1)))==0)){
                        news=j|(1<<(u-1));
                        d[i][news]+=d[i-1][j];//如果能放棋子,那么可行解就加上能到这个子状态的解
                    }
                }
            }
        }
        int ans=0;
        for(int i=0;i<sta;++i){
            if(legal[i]==k){
                ans+=d[n][i];
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

POJ3254

题目大意就是在一块区域内选出不限个数的格子(至少为1)且格子间互不相连,问有多少种选法。区域内有部分不能选择。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
#include <vector>
#include <queue>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <bitset>
#define INF 0x3f3f3f3f
#define ULL unsigned long long
#define LL long long
#define N 10100
#define MOD 100000000
#define eps 10e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
const double PI=acos(-1.0);
int m,n,uppr;
int way[600];
int dp[15][600];
int cur[15];
inline bool ok(int x){//若此状态没有两个1相邻,则可行
    return !(x&(x<<1));
}
void init()
{
    uppr=0;
    int mx=1<<n;
    for(int i=0;i<mx;++i){
        if(ok(i)){
            way[uppr++]=i;//预处理出所有可行的组合,因为上界在运行时确定可以节省掉DP时候的冗余,所以此处的init()是针对每一次运行重新调用以确定上界
        }   
    }
}
int main()
{
    int temp;
    ios::sync_with_stdio(false);
    while(cin>>m>>n){
        init();
        mem(dp,0);
        for(int i=1;i<=m;++i){
            cur[i]=0;
            for(int j=1;j<=n;++j){
                cin>>temp;
                if(temp==0){
                    cur[i]|=(1<<(n-j));//把每一行能走的位置标出来,这里采用相反的原因是如果此处是0则此处也不可行,但是不处理就会被忽略掉,具体看之后的代码
                }
            }
        }
        for(int i=0;i<uppr;++i){
            if(!(way[i]&cur[1])){
                dp[1][i]=1;//因为第一排没有子问题,单独处理
            }
        }
        for(int i=2;i<=m;++i){
            for(int j=0;j<uppr;++j){
                if((way[j]&cur[i])!=0){
                    continue;
                }
                for(int k=0;k<uppr;++k){
                    if(((way[k]&cur[i-1])!=0)||((way[k]&way[j])!=0)){//若能放在这一排并且和上一排不冲突,就做下面的,否证continue。就是为了此处能用一个&处理掉所以前面那样写
                        continue;
                    }
                    dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;//状态转移
                }
            }
        }
        int ans=0;
        for(int i=0;i<uppr;++i){
            ans=(ans+dp[m][i])%MOD;
        }
        cout<<ans<<endl;
    }
    return 0;
}

感觉还没体会到真谛,之后慢慢补齐……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值