【轮廓线DP】SRM671 D1L3 BearDestroys

题意:

在一个N*M的矩阵中,每个位置有一棵树,以及一个字符’S’或’E’
一颗树被推倒后,会占据其周围的一个格子(向右推占右边的,向下推占下边的)

但是如果那个方向有被推倒的树,则不能推倒这棵树。如果这个格子被其他格子的树占到了,也不能推倒。

现在有一头熊,从第一行开始,从左往右依次推倒每一个格子中的树。
首先,它会尽可能使用这个格子中字符的方向,如果不能再使用另一个方向。

现在求对于所有的方案,被推倒的树的个数之和。


分析:

倾斜的轮廓线DP题。。。
DP[x][y][mask]表示,在(x,y)位置,且轮廓线上被选择情况为mask时的方案数(另外还要用一个数组存答案)

为了方便起见,我们不考虑轮廓线上没有被覆盖的格子中的符号。

以一条反对角线为DP的轮廓线

在这里插入图片描述
处理红色格子时,轮廓线应有的形状。

首先,如果B格未被占据,那么必然会有一棵树在B和红色格上。

此时如果C被占据,那么B用任意一个字符均可,若C未被占据,那么B只能用‘S’

如果A格未被占据,那么可以选择在红色格和B上放一颗树。若此时A在最后一行,那么A用任意一个字符都行,否则只能用‘E’

另外,被覆盖的格子(红色格)用任意一种颜色均可。

最后,在每一个对角线末尾时:

在这里插入图片描述
此时X如果为空,那么填任意一个字符均可。

最后,为了简单期间,我们可以把矩阵换成这个样子:
在这里插入图片描述
其中红色部分是原图。

然后,就可以过了。。。

(上面最后一个步卡了我半个多小时。。。)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 50
#define MAXS 65536
using namespace std;
typedef long long ll;
ll dp[2][MAXS],num[2][MAXS];
int n,m,now;
ll mod;
int main(){
	SF("%d%d%lld",&n,&m,&mod);
	dp[now][(1<<(m+1))-1-1]=1;
	for(int i=0;i<n+m;i++){
		for(int j=1;j<m;j++){
			now^=1;
			memset(dp[now],0,sizeof dp[now]);
			memset(num[now],0,sizeof num[now]);
			for(int mask=0;mask<(1<<(m+1));mask++){
				int tag1=(mask>>(j-1))&1;
				int tag2=(mask>>j)&1;
				int tag3=(mask>>(j+1))&1;
				if(i-j<0||i-j>=n){
					(dp[now][mask|(1<<j)]+=dp[now^1][mask])%=mod;
					(num[now][mask|(1<<j)]+=num[now^1][mask])%=mod;
				}
				else if(tag2==0){
					(dp[now][mask|(1<<j)]+=2*(1+tag1)*dp[now^1][mask])%=mod;	
					(num[now][mask|(1<<j)]+=2*(1+tag1)*num[now^1][mask]+2*(1+tag1)*dp[now^1][mask])%=mod;
				}
				else{
					if(tag3==0){
						(dp[now][mask|(1<<j)|(1<<(j+1))]+=2*(1+(j==m-1))*dp[now^1][mask])%=mod;
						(num[now][mask|(1<<j)|(1<<(j+1))]+=2*(1+(j==m-1))*num[now^1][mask]+2*(1+(j==m-1))*dp[now^1][mask])%=mod;
					}
					if(tag3!=0||j!=m-1){
						(dp[now][mask&(~(1<<j))]+=dp[now^1][mask])%=mod;
						(num[now][mask&(~(1<<j))]+=num[now^1][mask])%=mod;
					}
				}
//				PF("[%d %d %d %lld %lld]\n",i-j,j,mask,dp[now^1][mask],num[now^1][mask]);
			}
		}
		memset(dp[now^1],0,sizeof dp[now]);
		memset(num[now^1],0,sizeof num[now]);
		for(int mask=0;mask<(1<<(m+1));mask++){
//			PF("[%d %d %lld %lld]\n",i,mask,dp[now][mask],num[now][mask]);
			int tag=(mask>>m)&1;
			int mask1=(mask<<1)&((1<<(m+1))-1);
			if(i+1-1>=n-1){
				(dp[now^1][mask1|1]+=(1+(tag==0))*dp[now][mask])%=mod;	
				(num[now^1][mask1|1]+=(1+(tag==0))*num[now][mask])%=mod;
			}
			else{
				if((mask1&2)==0){
					(dp[now^1][mask1|3]+=2*(1+(m==1))*(1+(tag==0))*dp[now][mask])%=mod;
					(num[now^1][mask1|3]+=2*(1+(m==1))*(1+(tag==0))*num[now][mask]+2*(1+(m==1))*(1+(tag==0))*dp[now][mask])%=mod;
				}
				if(m==1&&i%2==0)
					continue;
				(dp[now^1][mask1]+=(1+(tag==0))*dp[now][mask])%=mod;
				(num[now^1][mask1]+=(1+(tag==0))*num[now][mask])%=mod;
			}
		}
		now^=1;
	}
	int ans=(num[now][(1<<(m+1))-1])%mod;
	int res=(dp[now][(1<<(m+1))-1])%mod;
	PF("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值