【学习笔记】状压dp

状压dp 学习日志 ---2019.1.21

标签(空格分隔): 状压dp


推荐blog:
【暖*墟】

状态压缩动态规划 状压DP

神仙巨佬讲位运算

lead into

位运算

为了更好的理解状压dp,首先介绍位运算相关的知识。

1.x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。

2.x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。

3.xy,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)2(10)=1(01)。

’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充

’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最右一位

常见的应用:

1.判断一个数字x二进制下第i位是不是等于1。
方法:if(((1<<(i−1))&x)>0)

将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。

2.将一个数字x二进制下第i位更改成1。
方法:x=x|(1<<(i−1))

3.把一个数字二进制下最靠右的第一个1去掉。
方法:x=x&(x−1)


例题1
玉米田Corn Fields

/*【洛谷p1879】玉米田
一块长方形的牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12)。
从中选择一些作为草地,但不会选择两块相邻的土地。
如果不考虑草地的总块数,一共有多少种选择方案?(0也是一种方案)。*/

#include<bits/stdc++.h>
#define mod 100000000    
using namespace std;
int n,m,tot,state[1500],dp[15][1500],ans,cur[15];

//dp表示当前最大值,第一维是行数,第二维是状态数,cur是每行的情况,state是预存的可能状态

/*这题也是用二进制来表示状态,先预处理:枚举一行内(不考虑地图因素)每一种状态,看看是否合法,合法的话就打个标记,以便后面操作.先处理第一行,枚举下面行的时候再枚举一遍上一行,看看两行放置是否合法,合法就累计上一行计数即可。*/

inline int read()
{
    int ans=0;    
    char last=' ',ch=getchar();
    while(ch<'0' || ch>'9') last=ch,ch=getchar();
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}

inline bool fit(int x,int k)  
{
    return !(state[x]&cur[k]);
}

inline void init()              
{       
    for (int i=0;i<(1<<n);i++)
        if (!(i&(i<<1)))
            state[++tot]=i;
}

int main()
{
    m=read();
    n=read();
    init();
    int k;
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++)
        {
            k=read();
            if (!k)
                cur[i]+=(1<<(n-j));
        }
    for (int i=1;i<=tot;i++)             
        if (fit(i,1))
            dp[1][i]=1;
    for (int i=2;i<=m;i++)                
        for (int j=1;j<=tot;j++)      
        {
            if (!fit(j,i))
                continue;
            for (k=1;k<=tot;k++)      
            {
                if (!fit(k,i-1))
                    continue;
                if (state[j]&state[k])
                    continue;
                dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;       
            }
        }
    for (int i=1;i<=tot;i++)
        ans=(ans+dp[m][i])%mod;
    printf("%d",ans);
    return 0;
}

例题2

Hie With The Pie

#include<bits/stdc++.h>
using namespace std;

/*t = statue - (1<<(i-1)) : 代表当前没有到达i店的statue的前一个状态。
d (statue , i) == max( d( t , j ) + m( j, i ) ) : j 代表状态t已经到达的店,也是状态t当前停留的店,+m( j, i) 经过了
这段距离 状态t 到达了状态statue,也就是 t +1<<(i-1) == statue;*/

#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=a;i<=b;i++)
int d[1<<12][12];
int m[12][12];
int n;

void Floyd()
{
    for(int k=0; k<=n; k++)
        for(int i=0; i<=n; i++)
            for(int j=0; j<=n; j++)
                m[i][j]=min(m[i][j],m[i][k]+m[k][j]);
}

int main()
{    
    while(~scanf("%d",&n))
    {
        for(int i=0; i<=n; i++)
            for(int j=0; j<=n; j++)
                scanf("%d",&m[i][j]);
        memset(d,inf,sizeof(d));
        Floyd();
        d[0][0]=0;
        rep(i,1,(1<<n)-1)
        
        for(int i=1; i<(1<<n); i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(i&(1<<(j-1)))
                {
                    int t=i-(1<<(j-1));
                    if(t==0)
                    {
                        d[i][j]=m[0][j];
                        continue;
                    }
                    for(int k=1; k<=n; k++)
                    {
                        if(t&(1<<(k-1)))
                        {
                            d[i][j]=min(d[i][j],d[t][k]+m[k][j]);
                        }
                    }
                }
            }
        }
        int ans=inf;
        for(int i=1; i<=n; i++)
            ans=min(ans,d[(1<<n)-1][i]+m[i][0]);
        printf("%d
",ans);
    }
}

例题3

互不侵犯
注:这是昨年坐过的题

/*【洛谷p1896】互不侵犯
国王能攻击到它上下左右、以及左上左下右上右下八个方向上一个格子。
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。*/

#include<bits/stdc++.h>
using namespace std;
int n,k;
long long dp[10][15000][80],king[15000],state[15000],tot,ans;        
//dp[i][j][k]表示第i行选j状态在这一行及之前摆上k个国王的方案总数
//state:状态,king:那一行的国王总数

inline int read()                               
{
    int x=0,f=1;
    char c=getchar();
    while (!isdigit(c))
        f=c=='-'?-1:1,c=getchar();
    while (isdigit(c))
        x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}

inline void init()                               
{
    tot=(1<<n)-1;
    for (int i=0;i<=tot;i++)
        if (!((i<<1)&i))                            
        {
            state[++ans]=i;
            int t=i;
            while (t)                            //记录国王个数
                king[ans]+=t%2,t>>=1;            //注意是右移一位
        }
}

int main()
{
    n=read(),k=read();
    init();
    for (int i=1;i<=ans;i++)                         
        if (king[i]<=k)
            dp[1][i][king[i]]=1;
    for (int i=2;i<=n;i++)                            //枚举行
        for (int j=1;j<=ans;j++)                        //枚举这行方案
            for (int p=1;p<=ans;p++)                //枚举上行方案
            {
                if (state[j]&state[p])continue;
                if (state[j]&(state[p]<<1))continue;
                if (state[p]&(state[j]<<1))continue;
                for (int s=1;s<=k;s++)                //枚举国王个数
                {
                    if (king[j]+s>k)continue;
                    dp[i][j][king[j]+s]+=dp[i-1][p][s];
                }
            }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=ans;j++)
            tot+=dp[i][j][k];
    printf("%lld",tot);
    return 0;
}
 


例题4
炮兵阵地

待填坑


原文地址:https://www.cnblogs.com/sjsjsj-minus-Si/p/11634729.html