状压Dp

入门题是一个摆棋盘得问题,n * m得棋盘中可以摆放1 * 2 和 2 * 1 得棋子,问你摆满有几种摆放得可能,比较特得是n得范围特别小,m得范围大一些,这就是一个标志,可以向状态压缩上靠拢。
首先得寻找一个状态,这个状态的值就只有0和1——可以用二进制来表示,就是2^n,所以这也是为什么n得范围会很小的原因了,我们可以遍历每一列,然后取舍当前列的摆放,用0001001表示对下一列得影响(横着放的影响),所以也就有了dp[列号][下一列的状态]

   for(int i = 1;i <= M;i++)//列得遍历
        {
            for(int j = 0;j < (1 << N);j++)//j——状态遍历
            {
                if(dp[i][j])
                {
                    dfs(0,i,j,0);
                }
            }
        }
然后对当前列得莫一种状态情况,进行深度搜索(递归搜索,寻早所有的可能)

void dfs(int i,int j,int state,int nex)//行,列,状态,下一列得状态
{
    if(i == N)
    {
        dp[j + 1][nex] += dp[j][state];
        dp[j + 1][nex] %= mod;
        //printf("dp[%d][%d] = %d\n",j + 1,nex,dp[j + 1][nex]);
        return;
    }

    if(((1 << i) & state) > 0)//当前位置已经被占了
    {
        dfs(i + 1,j,state,nex);
    }

    if(((1 << i) & state) == 0)//摆放2 * 1 得可能
    {
        dfs(i + 1,j,state,nex | (1 << i));
    }

    if(i + 1 < N && ((1 << i) & state ) == 0 && ((1 << (i + 1)) & state) == 0)//摆放 1 * 2 得可能
    {
        dfs(i + 2,j,state,nex);
    }
    return;
}

其实到了这感觉状压Dp就像一个暴力,进入了下一题,是让你求最小总价值,n个人(n < 16)互相传递,a - 》 b是要消耗价值得,消耗的价值是知道得,让求传完所有人后所需要的最小总价值一开始觉得像最小生成树,要不是在这个专题里,我觉得一开始我会用最小生成树去做,顶多16个人,应该不会TE
其实用状压DP也挺好想得,000110011表示得是当前的状态——谁被传递了,谁没有被传递,我们还得知道现在被传递给了谁所以就有了dp[当前状态][在谁手上]

三曾for循环第一层遍历所有的状态,第二层遍历当前状态上所有可能拥有得人,第三次遍历所有可能传递得人

for(int i = 0;i < (1 << N);i++)
        {
            for(int j = 0;j < N;j++)
            {
                if(dp[i][j] != -1)
                {
                    for(int k = 0;k < N;k++)
                    {
                        if(!(i & (1 << k)))
                        {
                            dp[i | (1 << k)][k] = Min(dp[i | (1 << k)][k],dp[i][j] + cost[j][k]);
                            if((i | (1 << k)) == ((1 << N) - 1)) ret = Min(ret,dp[i | (1 << k)][k]);
                        }
                    }
                }
            }
        }

第二次循环你得做一个判断因为我一开始是这么初始化得

init();
        for(int i = 0;i < N;i++)
        {
            for(int j = 0;j < N;j++)
            {
                scanf("%d",&cost[i][j]);
            }
        }
        for(int i = 0;i < N;i++)
        {
            dp[1 << i][i] = 0;
        }
        int ret = -1;
void init()
{
    memset(dp,-1,sizeof(dp));
    memset(cost,0,sizeof(cost));
}
int Min(int a,int b)
{
    if(a == -1)return b;
    if(b == -1)return a;
    return min(a,b);
}

i状态下j得拥有者不能是-1,因为我更新的时候也是更新的是没有被传递得人,重复传递得情况就不会出现了
自定义一个MIn函数如果比较中出现了-1得时候,将会返回另一个值,运用的位运算比较多,还得多理解一下位运算

#include 
#include 
using namespace std;
const int Max = 20;
int cost[Max][Max];
int dp[1 << 20][Max];
int N;
void init()
{
    memset(dp,-1,sizeof(dp));
    memset(cost,0,sizeof(cost));
}
int Min(int a,int b)
{
    if(a == -1)return b;
    if(b == -1)return a;
    return min(a,b);
}
int main()
{
    while(~scanf("%d",&N))
    {
        init();
        for(int i = 0;i < N;i++)
        {
            for(int j = 0;j < N;j++)
            {
                scanf("%d",&cost[i][j]);
            }
        }
        for(int i = 0;i < N;i++)
        {
            dp[1 << i][i] = 0;
        }
        int ret = -1;
        for(int i = 0;i < (1 << N);i++)
        {
            for(int j = 0;j < N;j++)
            {
                if(dp[i][j] != -1)
                {
                    for(int k = 0;k < N;k++)
                    {
                        if(!(i & (1 << k)))
                        {
                            dp[i | (1 << k)][k] = Min(dp[i | (1 << k)][k],dp[i][j] + cost[j][k]);
                            if((i | (1 << k)) == ((1 << N) - 1)) ret = Min(ret,dp[i | (1 << k)][k]);
                        }
                    }
                }
            }
        }
        if(ret == -1)
            printf("0\n");
        else
            printf("%d\n",ret);
    }
    return 0;
}

接下来是第三题


BFS + 状压DP

是一个逃出升天得BFs,和基础BFS不同的是地图中出现了10把钥匙,10个门(最坏的情况),很明显嘛,状态来了000111表示得是钥匙得拥有情况——回顾一下bfs

#include 
#include 
#include 
#include 
using namespace std;
int mp[25][25];
int key[25][25];
int door[25][25];
int vis[25][25][1 << 11];
int foot[4][2] = {1,0,-1,0,0,1,0,-1};
struct Node{
    int x;
    int y;
    int step;
    int key;
    Node(){}
    Node(int a,int b,int s,int k) : x(a),y(b),step(s),key(k){}
};
queue q;
int n,m,t,sx,sy,ex,ey;
void init()
{
    memset(mp,0,sizeof(mp));
    memset(key,0,sizeof(key));
    memset(door,0,sizeof(door));


}
int main()
{
    char c;
    while(~scanf("%d%d%d",&n,&m,&t))
    {
        getchar();
        init();
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j < m;j++)
            {
                scanf("%c",&c);
                if(c == '*')mp[i][j] = 1;
                if(c == '@')sx = i,sy = j;
                if(c == '^')ex = i,ey = j;
                if(c >= 'A' && c <= 'J')door[i][j] =(1 << (c - 'A'));
                if(c >= 'a' && c <= 'j')key[i][j] =(1 << (c - 'a'));
            }
            getchar();
        }
        int ret = bfs();
        if(ret >= t)ret = -1;
        printf("%d\n",ret);
        getchar();
    }
    return 0;
}


从大佬那里学到了,把这个字符地图转换成了0,1地图,钥匙和门也为了位运算进行了转换和单独列出

重点在于搜索的时候如何进行钥匙和门得匹配——还是位运算
下一步我要到x,y去,有以下几种情况
看看是不是墙——easy
是不是门——是门得话还得匹配以下钥匙——可以看一下door数组中存的是门得二进制状态,用当前的状态去&门得状态——》如果是门返回值就是door[x][y]——》不是门返回得肯定是0也是door[x][y]——》推出
是不是钥匙——是钥匙得化还得更新状态——》有没有钥匙一或就知道了

int bfs()
{
    while(q.size())q.pop();
    memset(vis,0,sizeof(vis));

    Node now = Node(sx,sy,0,0);
    q.push(now);

    while(q.size())
    {
        now = q.front();
        q.pop();

        if(now.x == ex && now.y == ey)return now.step;

        for(int i = 0;i < 4;i++)
        {
            int nex = now.x + foot[i][0];
            int ney = now.y + foot[i][1];
            if(if_go(nex,ney,now.key))
            {
                //cout<<"CS"<



你可能感兴趣的:(算法入门)