状态压缩dp简(入门)

以下是我对这位大佬博客的理解,代码注释删了很多,感觉状压简直太妙了,尤其是第二题,简直把二进制性质用活了,同时把题目链接拷过来了

状态压缩动态规划,就是我们俗称的状压DP,是利用计算机二进制的性质来描述状态的一种DP方式

很多棋盘问题都运用到了状压,同时,状压也很经常和BFS及DP连用,有了状态,DP就比较容易了

总之就是二进制数代表一个状态,每位数上的1,0,代表着一个物品当前的状态

例题:

位运算例题(结合BFS):P2622 关灯问题II

题目:https://www.luogu.org/problemnew/show/P2622

题目描述

现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。

现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。

输入输出格式

输入格式:
前两行两个数,n m

接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。

输出格式:
一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1

那么这题就用一个dp代表一个状态,当dp==0时也就是n盏灯的状态为00000000000,就是全部关闭了

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3000;
int num,m,numd;
struct node{
    int dp,step;
    node(int d,int a)
	{
		dp=d,step=a;
	 } 
	 node(){
	 }
};
int vis[maxn];
int ma[maxn][maxn];
void bfs(int n){
    queue<node>que;
    node fir;
    que.push(node(n,0));
    while(!que.empty()){
        node u = que.front();
        que.pop();
        int pre = u.dp;
        for(int i = 1;i <= m;i++){//ö¾Ùÿ¸ö²Ù×÷
            int now = pre;
            for(int j = 1;j <= num;j++){
                if(ma [i][j] == 1){
                    if( (1 << (j - 1)) & now){
                        now = now ^ (1 << (j - 1));
                        }
                    }
                else if(ma[i][j] == -1){
                    now = ( (1 << (j - 1) ) | now);
                    }
            }
            if(vis[now] == true)   continue;
            if(now == 0){//´ïµ½Ä¿±ê״̬
                vis[0] = true;   
                cout<<u.step + 1<<endl;
                return ;
                }
            que.push(node(now,u.step+1));
            vis[now] = true;
            }
        }
    }
int main(){
	cin>>num>>m; 
    int n = (1 << (num)) - 1;
    for(int i = 1;i <= m;i++)
    for(int j = 1;j <= num;j++)
        cin>>ma[i][j];
    bfs(n);
    if(vis[0] == false)
        cout<<-1<<endl;
    return 0;
}

再来看一道二进制特别灵活的题:

P1879 [USACO06NOV]玉米田Corn Fields

题目:https://www.luogu.org/problemnew/show/P1879

题目描述

Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can't be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入输出格式

输入格式:
第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

输出格式:
一个整数,即牧场分配总方案数除以100,000,000的余数。

先预处理暴力可行的情况:vis,然后匹配题目给的情况:ma,

然后判断一行内合法的方法就是  (vis[j]) & (j & ma[i]) == j)

判断两行之间合法的情况为(k & j) == 0),可能会有疑问,为什么这里不加个vis[k]==1条件呢?没关系,k是前一行的情况,若vis[k]==0,dp[i - 1][k]=0,所以对dp[i][j]没有影响

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const int maxn = 4000+10,mod= 100000000;
int t[20][20],ma[20],dp[20][maxn],n,m;
bool vis[maxn];
int main(){
	cin>>n>>m; 
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
        	cin>>t[i][j];
            ma[i] = (ma[i] << 1) + t[i][j];//把每一行变成2进制 
        }
    }
    int mx = (1 << m) - 1;
    for(int i = 0;i <= mx;i++){//处理一行内可以种植的情况 010则这个1的位置可以种植.011,111,110,101中间位置不可种植 
        if((((i << 1) & i) == 0) & (((i >> 1) & i) == 0)){
            vis[i] = true;
        }
    }
    for(int i = 0;i <= mx;i++){
        if((vis[i]) &&((i & ma[1]) == i)){//(i & ma[1]) == i)什么鬼意思,可行的种植与题目给的种植 
            dp[1][i] = 1;
        }
    }
    for(int i = 2;i<=n;i++){
        for(int j = 0;j <= mx;j++){
            if((vis[j]) && (j & ma[i]) == j){//一行内合法 
                for(int k = 0;k <= mx;k++){
                    if((k & j) == 0){//两行之间各自合法 
                        dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;//dp过程,看懂了,天秀啊 
                    }
                }
            }
        }
    }
    ll ans = 0;
    for(int i = 0;i <= mx;i++){
        ans = (ans + dp[n][i]) % mod;//答案在最后一行
    }
    cout<<ans<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41286356/article/details/86530411