状态压缩dp总结 长期更新




状压dp本人做的题目真的不太多...至今还未理解到其中的精髓.所以以下的思路描述中有存在不当的地方希望能够指出.另外,有些地方说的比较复杂,因为本弱鸡

对这些东西不是很理解.....多写点有助于理解吧.


POJ 1185  经典状压dp     我队友这篇博文还不错.

 

思路:

 

首先,我们可以发现对于每一行的当前位置能不能放炮兵,只与他的上一行和上上一行

的炮兵位置有关系,所以要开一个三维数组转移关系.

0表示不放大炮,1表示放大炮,同样的,先要满足硬件条件,即有的地方不能放大炮,

后就是每一行中不能有两个1的距离小于2(保证横着不互相攻击),这些要预先处理一下。然后就是

状态表示和转移的问题了,因为是和前两行的状态有关,所以要开个三维的数组来表示状态,当前行

的状态可由前两行的状态转移而来。即如果当前行的状态符合前两行的约束条件(不和前两行的大炮

互相攻击),则当前行的最大值就是上一个状态的值加上当前状态中1的个数(当前行放大炮的个数) 


【状态表示】dp[i][j][k] 表示第i行为第j个状态,第i-1为第k个状态时的最大炮兵个数。 

【状态转移方程】 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);//num[j]为第j个状态中的二进制1的个数

【DP边界条件】dp[1][i][0] =num[i] 状态i能够满足第一行的硬件条件

(注意:这里的i指的是第i个状态,不是一个二进制数,开一个数组保存二进制状态) 


思考:



对于这种有地形限制的,标记一个点其他周围都有影响的,一般都要预处理.将所有可能的状态和地形的限制预处理出来,每次枚举第i个状态的时候,要首先满足地形的限制,在满足各行各列之间的关系.

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=111;
int dp[maxn][maxn][maxn];//dp[i][j][k] 第i行为第j个状态,第i-1行为第k个状态时的最大炮兵数 
int status[maxn],num[maxn]; //status[i] 当前第i个状态的二进制 kk(1放大炮 0 不放).  
//num[i]存放与status相对应的当前的第i个状态的二进制中有多少个1,即放了多少炮兵 
char s[maxn];
int mp[maxn];//存放地形的限制,1表示不能放大炮,0表示可以放大炮. 
int n,m;
int _count(int x)
{
    int __=0;
    while(x)
    {
        if(x&1)
            __++;
        x>>=1;
    }
    return __;
}
int main()
{
    while(~scanf("%d %d",&n,&m))
    {
        memset(dp,-1,sizeof(dp));
        memset(status,0,sizeof(status));
        memset(num,0,sizeof(num));
        memset(mp,0,sizeof(mp));
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s);
            for(int j=0;j<m;j++)
            {
                if(s[j]=='H')
                    mp[i]+=(1<<j);
            }
        }
        int cnt=0;
        for(int i=0;i<(1<<m);i++)
        {
            if(!(i&(i<<2))&&!(i&(i<<1)))//一行内的炮兵不能相互攻击. 
            {
                num[cnt]=_count(i);
                status[cnt++]=i;
            }
        }
        for(int i=0;i<cnt;i++)
        {
            if(!(status[i]&mp[1]))
            dp[1][i][0]=num[i];//第一行赋初值.
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=0;j<cnt;j++)//第i行的第j个状态
              {  if(status[j]&mp[i]) //第i行与第i行的地形不冲突
                    continue;
	            for(int k=0;k<cnt;k++)//第i-1行的第k个状态
	            {
	                if(status[k]&mp[i-1]) //状态与地形不冲突 
	                    continue;
	                if(status[j]&status[k]) //第i行与第i-1行不互相攻击 
	                continue;
	                for(int t=0;t<cnt;t++)//第i-2行的第t个状态
	                {
	                    if(status[t]&mp[i-2]) //状态与地形不冲突
	                    continue;
	                    if(status[t]&status[k]) //第i-1行与i-2不互相攻击 
	                    continue;
	                    if(status[t]&status[j]) //第i行与第i-1行不互相攻击.
	                    continue;
	                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);
	                }
	            }
        	}
        }
        int ans=0;
        for(int i=0;i<cnt;i++)
            for(int j=0;j<cnt;j++)
            {
            
                ans=max(ans,dp[n][i][j]);
            }
            printf("%d\n",ans);
            
    }
    return 0;
}



西电oj 1038


思路:

这个题目和炮兵阵地那个题目很像很像,不过这个是要割出来2*2的.还是老规矩,我们先来确定一下需要几维.由于是2*2,那么每次割玻璃只会对下面一行有影响.也就是说当前行能否割,只与上一行的状态有关了,所以二维就足够了。


其次因为这是一个2*2的我们枚举1表示切方格的时候只能枚举四个中的一小块,所以这里我设当第i行第j位为1的时候,那么他就按照这样切,表示它和第i+1行的 j-1 j 四块,构成一个2*2.

01

00

那么我们最后只需要统计第n-1行的所有可能状态中二进制数最多的那个.另外我们发现它只对三个方向和自身地形,4个位置不能冲突.另外需要注意的就是按照我们这种的

切割方式它最前面永远不可能是1只能是0.所以我们枚举状态的时候只要枚举到(1<<(m-1))就可以了.

根据炮兵阵地的总结,存在这种地形限制问题的, 我们都需要进行预处理,即把所有可能的情况预处理出来,把自己地形的限制预处理出来,(能切割的地方为0,不能切割的地方为1).

最后给出状态转移方程:

dp[i][j]=max(dp[i][j],dp[i-1][k]+num[j])

//dp[i][j]表示第i行的第j个状态.(是所有可行状态中的第j个,并不代表一个二进制数)


#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1024;
int dp[maxn][maxn];
int status[maxn],num[maxn];
int mp[maxn];
int t,n,m;
int __(int x)
{
    int _=0;
    while(x)
    {
        if(x&1)
            _++;
        x>>=1;
    }
    return _;
}
int check_row(int r,int i)//判断第r行的第i个状态和r的地形是否冲突
{
    int sta=status[i];
    if(mp[r]&sta||mp[r+1]&sta||mp[r]&(sta<<1)||mp[r+1]&(sta<<1)) return 0;
    return 1;
}
int check_sta(int i,int j)//判断第i个状态和第j个状态是否冲突。
{
    int s1=status[i],s2=status[j];
    if(s1&s2||s1&(s2<<1)||(s1<<1)&s2) return 0;
    return 1;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        memset(mp,0,sizeof(mp));
        memset(status,0,sizeof(status));
        memset(num,0,sizeof(num));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            int x;
            for(int j=0;j<m;j++)
            {
                scanf("%d",&x);
                if(!x)
                    mp[i]+=(1<<j);
            }
        }
        int cnt=0;
        for(int i=0;i<(1<<(m-1));i++)
        {
            if(!(i&(i<<1)))
            {
                num[cnt]=__(i);
                status[cnt++]=i;
            }
        }
        for(int i=0;i<cnt;i++)
        {
            if(check_row(1,i))
            dp[1][i]=num[i];

        }
        for(int i=2;i<=n;i++)
        {
            for(int j=0;j<cnt;j++)//枚举第i行的状态
            {
                if(!check_row(i,j))//判断枚举的当前行地形和第j个状态是否冲突
                    continue;
                for(int k=0;k<cnt;k++)//枚举第i-1行的状态.
                {
                    if(!check_row(i-1,k))//判断第i-1行的地形和当前k状态是否冲突.
                    continue;
                    if(!check_sta(j,k))//判断上一行的状态k和当前行状态j是否冲突
                    continue;
                    dp[i][j]=max(dp[i][j],dp[i-1][k]+num[j]);
                }
            }
        }
        int ans=0;
        for(int i=0;i<cnt;i++)
            ans=max(ans,dp[n-1][i]);
        printf("%d\n",ans);
    }
    return 0;
}





猜你喜欢

转载自blog.csdn.net/howardemily/article/details/75691597