状压DP入门

状压DP:

神奇的DP方式,简单来说就是用二进制来简单压缩状态,然后根据题目可能会有一点点改变。

但是不一定是用二进制,还有一些以压缩状态为思想的题目。

首先参考:

https://blog.csdn.net/Stockholm_Sun/article/details/78213290

http://www.cnblogs.com/Ronald-MOK1426/p/8451875.html

https://blog.csdn.net/qq_36183935/article/details/70904217

https://www.cnblogs.com/Ronald-MOK1426/p/8456945.html

比较重要的状态转移和状态判断。

第一道题 洛谷 

P1879 入门题 Corn Fields

Description

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.

Input

Line 1: Two space-separated integers:M and N
Lines 2..M+1: Line i+1 describes row i of the pasture with N space-separated integers indicating whether a square is fertile (1 for fertile, 0 for infertile)

Output

Line 1: One integer: the number of ways that FJ can choose the squares modulo 100,000,000.

Sample Input

2 3

1 1 1

0 1 0

Sample Output

9

Hint

Number the squares as follows:

1 2 3

  4 


There are four ways to plant only on one squares (1, 2, 3, or 4), three ways to plant on two squares (13, 14, or 34), 1 way to plant on three squares (134), and one way to plant on no squares. 4+3+1+1=9.

用二进制存储状态去用上面的各种位运算用于判断,预处理所有状态然后把这些状态依次放进去首先判断满足不相邻条件,再判断是否会与上面相邻,并且用上面的进行转移。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<cstdlib>
#include<cmath>
#include<set>
#include<queue>
#include<vector>

#define MOD 100000000

using namespace std;

int n,m,tot,state[1500],dp[15][1500],ans,cur[15];

bool fit(int x,int k)
{
	return !(state[x]&cur[k]);//判断当前状态是否符合当前行 
    //不放牛的地方绝对不可以放牛否则就会为1 
}

void init()
{
	tot=0;
	int sum=1<<n;  //预处理所有可能状态 
    for(int i=0;i<sum;i++)
    {
    	if(!(i&(i<<1)))state[++tot]=i;//不相邻 
    }
}

int main()
{
	int k;
    cin>>m>>n;
    init();
    for(int i=1;i<=m;i++)
       for(int j=1;j<=n;j++)
       {
       	    cin>>k;
       	    if(!k)
       	    {
    	       	cur[i]+=(1<<(n-j));//当不放牛状态就是1 
            }    
       }
	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;	     
}

第二道题

 http://www.cnblogs.com/Ronald-MOK1426/p/8456798.html

【洛谷P1896【SCOI2005】】互不侵犯King

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

输入输出格式

输入格式:

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式:

所得的方案数

输入输出样例

输入样例#1

3 2

输出样例#1

16

解题方法差不多也是转移从上一行下来的01串,就是判断是否满足条件多了左上左下等而已。

事实上,中间有一步我处理重复0的情况不加也能AC,不知道为什么。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>


using namespace std; 
typedef long long LL;
typedef unsigned long long ULL;

long long state[1500],dp[10][1500][85],n,k,tot,ans=0,king[1500]; 

void init()
{
	memset(king,0,sizeof(king));
	tot=(1<<n)-1;//01串所有的状态 
	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()
{
    cin>>n>>k;
	init();
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=ans;i++)
	{
		if(king[i]<=k)
		dp[1][i][king[i]]=1;
	}
	for(int i=1;i<=n;i++)
	{
		dp[i][1][0]=1;
	}  //由于重复0情况不能相加,需要主动赋值。 
	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;//上下相邻:j作为这一层状态,p作为上一层状态 
				if((state[j]<<1)&state[p])continue;//判断有没有右下 
				if((state[j])&(state[p]<<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];
				}//枚举这一行状态出现之前已经出现的国王可能数。
				//i-1行之前比如出现了一个,出现了两个 ,出现了三个,然后加上第i行状态。 
				//不能枚举0,因为0是无效的,每一行加入0会进行重复加,比如第三行2个然后满了的情况,第一第二行分别会加一种情况,第三行2个加上前两个空的情况就不只是算
				//一次了,而实际上这只算一次,所以这种0的情况通过初始化第一行,不放元素然后一直不放元素作为1种 
				
				//防止重复只能从1开始,但是对于每次这种前面全是0的情况我们需要赋值为一种情况。 
			}
		}
	}
	
	LL sum=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=ans;j++)
		{
			sum+=dp[i][j][k];
		}
	} 
	cout<<sum<<endl;
}

 

P2704 [NOI2001]炮兵阵地 

基础偏一点处理

处理两行的状态,因为需要考虑判断的是左两个(预处理状态的时候就可以排除),还有上面两个,需要存下相邻两行状态,然后递推就可以也满足右下,相当于之后的左上。

然后动规方程:dp[第i行][第i行状态][第i-1行状态]=max(dp[i][i状态][i-1状态],dp[i-1][i-1状态][i-2状态]+i状态炮兵数目)

然后第几行需要作为滚动数组,防止MLE,因为其实动规方程递推的时候只需要考虑前一行就可以,dp[2][1<<10+1][1<<10+1]的容量就已经完全满足了。

细节判定条件和之前基本一致。本题貌似单字符处理会WA,有些玄学,反正我处理这道题花了很久,明明基本都写出来了,都困在字符串处理上,用cin gets getchar 效果有时候居然不同。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>


using namespace std; 
typedef long long LL;
typedef unsigned long long ULL;

long long dp[3][1<<10][1<<10],num[1<<10];
long long cur[1<<10+1];
long long state[1<<10+1];
int n,m;
int ans=0;
int fit(int i,int j)
{
    return !(state[i]&cur[j]); 
} //如果!内的项为0 说明没有放错位置。 

void init()
{
    memset(dp,0,sizeof(dp));
    memset(num,0,sizeof(num));
    memset(cur,0,sizeof(cur));
    int tot=(1<<m)-1;
    for(int i=0;i<=tot;i++)
    {
        if( ( (i<<1)&i)||( (i<<2)&i) )
        {
            ;
        }
        else 
        {
            state[++ans]=i;
            int t=i;
            while(t)
            {
                num[ans]+=t%2;
                t>>=1;
            }
        }
    }
}

int main()
{

    cin>>n>>m;
    init();
    char s[200];
    if(n==0&&m==0)
    {
    	cout<<"0"<<endl;
    	return 0;
    }
    for(int i=1;i<=n;i++)
    {
    	cin>>s;
    	for(int j=0;j<strlen(s);j++)
    	{
        	if(s[j]=='H')
        	cur[i]+=1<<(m-j-1);
        }
    } 
    for(int i=1;i<=ans;i++)
    {
    	if(fit(i,1))
    	dp[1][i][0]=num[i];
    }
    //先处理第二行
    for(int i=1;i<=ans;i++)
     for(int j=1;j<=ans;j++)
     {
        if(fit(i,2)&&fit(j,1))
        if(!(state[i]&state[j]))
        {
          dp[0][i][j]=num[i]+dp[1][j][0];
        //  cout<<dp[0][i][j]<<endl;
          //cout<<dp[2][i][j];
        }
     }
     if(n==1)
     {
    	long long A=0;
    	for(int i=1;i<=ans;i++)
    	{
	    	if( fit(i,1) )
	    	A=max(A,num[i]);
	    }
	    cout<<A<<endl;
	    return 0;
     }
     if(n==2)
     {
     	long long A=0;
     	for(int i=1;i<=ans;i++)
     	 for(int j=1;j<=ans;j++)
     	 {
 	     	A=max(A,dp[0][i][j]);
 	     }
 	     cout<<A<<endl;
		  return 0; 
     }
    for(int i=3;i<=n;i++)
    {
    	for(int j=1;j<=ans;j++)
    	{
        	if(fit(j,i))
        	{
        		for(int k=1;k<=ans;k++)
                { 
                    if(!(state[j]&state[k]))
                    if(fit(k,i-1))
                     for(int x=1;x<=ans;x++)
                     if(!(state[k]&state[x]))
                     if(!(state[j]&state[x]))
                      if(fit(x,i-2))
                      {
                      	 if(!(state[x]&state[k]))
                         dp[i%2][j][k]=max(dp[i%2][j][k],dp[(i-1)%2][k][x]+num[j]);		
                      }
                } 
        	}
        	
        }
    }
    
    long long sum=0;
    for(int i=1;i<=ans;i++)
    {
    	for(int j=1;j<=ans;j++)
    	sum=max(sum,dp[n%2][i][j]);
    }
    cout<<sum<<endl;
}

入门剩余题:

UESTC 数据结构专题一道状压DP

洛谷— —过河:扩展欧几里得+离散化(有点难)

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值