LOJ#2833 「JOISC 2018 Day 1」帐篷 dp

题目描述
译自 JOISC 2018 Day1 T3「テント / Tents」
JOI 君经营着一座露营地。露营地被划分为 H 行 W 列的矩阵,各行平行于东西方向,而各列平行于南北方向。从北向南数第 i i i 行和从东向西数第 j j j 列相交的格子表示为 ( i , j ) (i,j) (i,j)

JOI 君想在格子里搭一些帐篷。每座帐篷必须占据刚好一个格子。没有两座帐篷会占据同一个格子。

每座帐篷在东、南、西、北四个方向之一有一个出入口。帐篷的出入口朝向必须满足以下条件:

如果格子 ( i 1 , j ) 和 ( i 2 , j ) ( 1 ≤ i 1 < i 2 ≤ H , 1 ≤ j ≤ W ) (i_{1},j) 和 (i_{2},j)(1\le i_{1} < i_{2}\le H,1\le j\le W) (i1,j)(i2,j)(1i1<i2H,1jW) 上都有帐篷,那么格子 ( i 1 , j ) (i_{1},j) (i1,j) 上的帐篷出入口必须朝南,而格子 (i_{2},j) 上的帐篷出入口必须朝北。
如果格子 ( i , j 1 ) 和 ( i , j 2 ) ( 1 ≤ i ≤ H , 1 ≤ j 1 < j 2 ≤ W ) (i,j_{1}) 和 (i,j_{2})(1\le i\le H,1\le j_{1} < j_{2}\le W) (i,j1)(i,j2)(1iH,1j1<j2W) 上都有帐篷,那么格子 ( i , j 1 ) (i,j_{1}) (i,j1) 上的帐篷出入口必须朝东,而格子 ( i , j 2 ) (i,j_{2}) (i,j2) 上的帐篷出入口必须朝西。
JOI 君想知道在露营地上搭起至少一顶帐篷的合法方案数。两种方案被认为是不同的,当且仅当有至少一个格子的状态(即是否存在帐篷或帐篷出入口的朝向)不同。

任务
给出露营地的相关信息,请求出在露营地上搭起至少一顶帐篷的合法方案数,对 1000000007 取模。

输入格式
仅一行两个以空格分开的整数 H 和 W,表示 JOI 君经营的露营地有 H 行 W 列。

输出格式
输出一行,仅一个整数,表示在露营地上搭起至少一顶帐篷的合法方案数对 1000000007 取模的余数。

分析:如果某一行有两顶帐篷,这两顶帐篷出口必须东西相对,那么这两顶帐篷所在的列不可能有其它帐篷(因为要求出口南北相对)。基于此,可以进行一个dp:

假设我们从上往下一行一行放帐篷。设大小为 ( i , j ) (i,j) (i,j)的营地的方法为 f [ i ] [ j ] f[i][j] f[i][j]
在计算 f [ i ] [ j ] f[i][j] f[i][j]时,不妨分类讨论:
(1)这一行是空的。这种情况有 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]种放法。
(2)这一行只有一顶帐篷,且这顶帐篷所在列也只有一顶帐篷。我们先把这一行的帐篷放好,有 4 ∗ j 4*j 4j种放法(算上方向)。那么这顶帐篷所在行列都不能放帐篷了,剩下 i − 1 i-1 i1行和 j − 1 j-1 j1列,因此总放法为 4 ∗ j ∗ f [ i − 1 ] [ j − 1 ] 4*j*f[i-1][j-1] 4jf[i1][j1]
(3)这一行有一顶帐篷,且这一列的前面某行也有一顶帐篷,这两顶帐篷南北相对。这一顶帐篷的放法有 j j j种,前面行帐篷的方法有 i − 1 i-1 i1种。把这两顶帐篷放好后,这两行一列就不能再放帐篷,剩下的放法为 f [ i − 2 ] [ j − 1 ] f[i-2][j-1] f[i2][j1]。因此总贡献为 j ∗ ( i − 1 ) ∗ f [ i − 2 ] [ j − 1 ] j*(i-1)*f[i-2][j-1] j(i1)f[i2][j1]
(4)这一行有两顶帐篷,则帐篷所在的两列不能有其它帐篷。同理,总贡献为 j ( j − 1 ) 2 f [ i − 1 ] [ j − 2 ] \frac{j(j-1)}{2}f[i-1][j-2] 2j(j1)f[i1][j2]

注意这样算出的结果可能全空,因此最后答案要减1。

代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define mod 1000000007
using namespace std;
typedef long long ll;
int read() {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=3005;
const int INF=0x3f3f3f3f;
const int MOD=10007;
int n,m,dp[Size][Size];
int main() {
	n=read();
	m=read();
	for(int i=0; i<=n; i++)	dp[i][0]=1;
	for(int i=0; i<=m; i++)	dp[0][i]=1;
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=m; j++) {
			dp[i][j]=dp[i-1][j];
			dp[i][j]=(dp[i][j]+4ll*j%mod*dp[i-1][j-1]%mod)%mod;
			if(j>=2)	dp[i][j]=(dp[i][j]+(ll)j*(j-1)/2%mod*dp[i-1][j-2]%mod)%mod;
			if(i>=2)	dp[i][j]=(dp[i][j]+(ll)j*(i-1)%mod*dp[i-2][j-1]%mod)%mod;
		}
	}
	printf("%d",(dp[n][m]-1+mod)%mod);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值