8.5信息学夏令营练习(状压dp)

状压dp的核心,就是把所有的行(不一定)转化成二进制。

其实这跟拼拼图很像。

所以,我们一定要熟悉位操作,今天就是因为不熟悉位运算,浪费了很多时间。

类     型

运算符

含义

位逻辑

运算符

&

按位与

|

按位或

^

按位异或

~

取反

移位运

算   符

<< 

左移

>> 

右移

 

总结起来说,就是先把它写成二进制(8位),然后按下表规则进行运算

类型

符号

规则

例子

按位与

&

同1为1,其余为0

9        00001001

&

5        00000101

1        00000001

按位或

|

同0则0,其余为1

9        00001001

|

5        00000101

13       00001101

按位异或

^

不同则1,相同则0

9        00001001

^

5        00000101

12       00001100

取反~

~

0变1,1变0

9        00001001

~

246      11110110

左移

<< 

丢掉最右,整体右移

9        00001001

>> 

2

      00000010

右移

>> 

丢掉最左,整体左移

9        00001001

<< 

2

         00100100

 还有,状压dp和dfs在很多题目都有联系!!!!

不多说:

T1:

题目大意:
       在一个n*m(1<=n,m<=12)的田地中种玉米,有些格子不能放,要求不能有任何两个玉米相邻,问有多少种方法(方法数mod 100000000)。
Sample input
2 3
1 1 1
0 1 0    
Sample output
9   
我们注意到每一行至多只有12个数字,我们可以用一个至多12位的二进制数来表示状态,对于每一位,1表示种了玉米,0表示没有。
例如:11001 表示这一行第1、4、5个格子种了玉米。
so,方程就可以推出了!
 f[i,j]表示第i行状态为j的可能性数
f[i,j]=sum(f[i-1,k])(注意,j和k在上下也要符合条件,要判断)
#include<iostream>
using namespace std;
bool ma[15][15];
long long num[13][4097];
int pd1[4097];
int cf[13],mh[13];
int main()
{
	int n,m,m2=1;
	cin>>n>>m;
	cf[0]=1;
	for(int i=1;i<m;i++)
	{
		cf[i]=cf[i-1]*2;
	}
	for(int i=0;i<m;i++)
	{
		m2*=m;
	}
	int g=0;
	for(int i=0;i<cf[m-1]*2;i++)
	{
		int x=i;
		bool bo=true;
		int ox=0,cx=0;
		for (;x>0;x-=(x-(x&(x-1))))
		{
			ox=cx;
			cx=(x&(-x));
			if(ox*2==cx)//判断是否符合条件。每道状压dp的题目基本上都有这一步!
			{
				bo=false;
			}
		}
		if(bo==true)
		{
		pd1[g++]=i;//同样很常见!			
		}
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			cin>>ma[i][j];
		}
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(ma[i][j]==0)
			{
				mh[i]+=cf[m-j-1];
			}
		}
	}
	for(int j=0;j<g;j++)
	{
		if(mh[0]&pd1[j])
		continue;
		num[0][j]++;			
	}
	for(int i=1;i<n;i++)
	{
		for(int j=0;j<g;j++)
		{
			if(mh[i]&pd1[j])
			continue;
			for(int k=0;k<g;k++)
			{
				if(pd1[j]&pd1[k])
				continue;//判断上下
				num[i][j]+=num[i-1][k];
			}				
			num[i][j]%=100000000;
		}
	}
	long long ans=0;
	for(int i=0;i<g;i++)
	{
		ans+=num[n-1][i];
	}
	ans%=100000000;
	cout<<ans<<endl;
	return 0;
}
T2
给出一个W行H列的广场
用1*2小砖铺盖,小砖之间互相不能重叠
问有多少种不同的铺法?
1<=W,H<=11
这道题目,我做了很久,因为我一直想记录每一列的竖砖空位数,但是,其实不需要。其格式同题一差不多,也要判断上下,也要判断左右是否符合。f[i][j][k]最好为第i行状态为j且上一行状态为k,但我不是......
我的方法,(麻烦)
#include<iostream>
using namespace std;
long long num[12][144][2048];
int pd1[2048];
int cf[12],w[2048];
int main()
{
    int m,n;
    for(;;)
    {
        int g=0;
        cin>>n>>m;
        if(m==0&&n==0)
        {
            break;
        }
        if(m>n)
        {
            swap(n,m);
        }
        if(n==1)
        {
            if(m%2==0)
            cout<<m/2<<endl;
            else
            cout<<0<<endl;
            continue;
        }
        memset(num,0,sizeof(num));
        memset(pd1,0,sizeof(pd1));
        memset(cf,0,sizeof(cf));
        cf[0]=1;
        for(int i=1;i<=m;i++)
        {
            cf[i]=cf[i-1]*2;
        }
        int m2=1;
        for(int i=0;i<m;i++)
        {
            m2*=2;
        }
        for(int i=0;i<m2;i++)
        {
            int temp=0;
            while(i>=cf[temp])
            {
                temp++;
            }
            w[i]=temp;
        }
        for(int i=0;i<cf[m-1]*2;i++)
        {
            int x=i;
            bool bo=true;
            int ox=0,cx=0;
            for (;x>0;x-=(x-(x&(x-1))))
            {
                ox=cx;
                cx=(x&(-x));
                if(ox==0)
                {
                    int temp2=cx;
                    while(temp2>=4)
                    {
                        temp2/=4;
                    }
                    if(temp2!=1)
                    bo=false;
                }
                if(ox!=0)
                {
                    int temp1=cx/ox;
                    while(temp1>=4)
                    {
                        temp1/=4;
                    }
                    if(temp1!=2)
                    bo=false;                    
                }
            }
            if((m-w[i])%2!=0)
            bo=false;
            if(bo==true)
            {
            pd1[g++]=i;            
            }
        }
        for(int j=0;j<g;j++)
        {
            num[0][j][pd1[j]]++;            
        }
        for(int i=1;i<n-1;i++)
        {
            for(int j=0;j<g;j++)
            {
                for(int k=0;k<m2;k++)
                {
                    if(num[i-1][j][k]>0)
                    {
                    for(int l=0;l<g;l++)
                    {
                        if(((pd1[j]^pd1[l])>0)&&((pd1[j]^k)>0)&&((k&(pd1[j]^pd1[l]))>0))
                        continue;
                        num[i][l][k^pd1[l]]+=num[i-1][j][k];
                    }                        
                    }
                }                
            }
        }
        for(int i=0;i<g;i++)
        {
            for(int j=0;j<m2;j++)
            {
                for(int k=0;k<g;k++)
                {
                        if(((pd1[i]^pd1[k])>0)&&((pd1[i]^j)>0)&&((j&(pd1[i]^pd1[k]))>0))
                        continue;
                        int temp=pd1[k]^j;
                        if(temp==0)
                        num[n-1][k][0]+=num[n-2][i][j];
                }
            }
        }
        long long ans=0;
        for(int i=0;i<g;i++)
        {
            ans+=num[n-1][i][0];
        }
        cout<<ans<<endl;
    }
    return 0;
}
他人的方法:
#include<iostream>
using namespace std;
int n,m;
long long f[13][13][2060];
int main()
{
    cin>>n>>m;
    while(n!=0&&m!=0)
    {
        if(n*m%2==1){cout<<0<<endl;cin>>n>>m;continue;}
        f[n+1][1][0]=1;
        for(int i=n;i>0;i--)
          for(int j=m+1;j>0;j--)
            for(int s=(1<<m)-1;s>=0;s--)
            {
                f[i][j][s]=0;
                if(j==m+1){f[i][j][s]=f[i+1][1][s];continue;}
                int p=m-j;
                if(((1<<p)&s)>0){f[i][j][s]=f[i][j+1][s-(1<<p)];continue;}
                f[i][j][s]=f[i][j+1][s+(1<<p)];
                if(p>0)
                    if(((1<<(p-1))&s)==0)f[i][j][s]+=f[i][j+2][s];
            }
        cout<<f[1][1][0]<<endl;
        cin>>n>>m;
    }
    return 0;
}

T3
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

Input

第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

Output

仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

Sample Input

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

Sample Output

6
我们发现,这次第i行的状态不仅会受到第i-1行的影响,还会受到i-2行的影响,所以我们要多加了一重循环来枚举第i-2行的状态,dp也要增加一维,好在按题中的影响范围,枚举状态后发现即使m=10也只有60种状态满足,因此100×60×60×60不会超时,且100×60×60也开的下。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int n,m,sta[65];
long long dp[105][65][65];
int map[105];
int sum[65];
char s[15];
int pd(int x)
{
    if(((x&(x<<2))!=0)||((x&(x<<1))!=0))
    return 0;
    return 1;
}
int main()
{
    int n,m;
    string st;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cin>>st;
        for(int j=0;j<m;j++)
        {
            if(st[j]=='H')
            map[i]+=(1<<j);
        }//不能放的为1,同下面要放的是1不同,方便做题,因为只要一个&就可以判断方案是否可行!
    }
    int tot=0;
    for(int i=0;i<(1<<m);i++)
    {
        if(pd(i))
        {
            sta[tot]=i;
            int x=i,temp=0;
            while(x>0)
            {
                temp++;
                x-=x-(x&(x-1));
            }
            sum[tot++]=temp;
        }
    }
    for(int i=0;i<tot;i++)
    {
        if((map[0]&sta[i])==0)
        dp[0][i][0]=sum[i];
    }
    for(int i=1;i<n;i++)
    {
        for(int j=0;j<tot;j++)
        {
            if((sta[j]&map[i-2])!=0)
            continue;
            for(int k=0;k<tot;k++)
            {
                if((sta[k]&map[i-1])!=0)
                continue;
                if((sta[k]&sta[j])!=0)
                continue;
                for(int l=0;l<tot;l++)
                {
                    if((sta[l]&map[i])!=0)
                    continue;
                    if((sta[l]&sta[j])!=0)
                    continue;
                    if((sta[l]&sta[k])!=0)
                    continue;
                    dp[i][l][k]=max(dp[i][l][k],dp[i-1][k][j]+sum[l]);//j,k,l是sta[]的下标,又是dp的下标,这是一种方法
                }
            }
        }
    }
    long long ans=0;
    for (int i=0;i<tot;i++)
    for (int j=0;j<tot;j++)
    {
        if ((map[n-2]&sta[i])!=0)continue;
        if ((map[n-1]&sta[j])!=0)continue;
        if ((sta[i]&sta[j])!=0)continue;
        ans=max(dp[n-1][j][i],ans);
    }
    cout<<ans<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值