车II【状态压缩】

Time Limit:1000MS Memory Limit:65536K
Total Submit:74 Accepted:40

Description
有一个nm的棋盘(n、m≤80,nm≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻。求合法的方案总数。


Input
n,m,k

Output
方案总数


Sample Input
3 3 2

Sample Output
24


解题思路
观察题目给出的规模, n 、 m ≤ 80 n、m≤80 nm80,这个规模要想用状态压缩是困难的,280无论在时间还是空间上都无法承受
然而我们还看到 n ∗ m ≤ 80 n*m≤80 nm80
稍微思考我们可以发现: 9 ∗ 9 = 81 > 80 9*9=81>80 99=81>80,即如果 n , m n,m n,m都大于等于9,将不再满足 n ∗ m ≤ 80 n*m≤80 nm80这一条件。所以,我们有n或m小于等于8,而 2 8 2^8 28是可以承受的!

我们假设 m ≤ n , n m≤n,n mnn是行数 m m m是列数,则每行的状态可以用m位的二进制数表示
但是本题和例1又有不同:例1每行每列都只能放置一个棋子,而本题却只限制每行每列的棋子不相邻
上例中枚举当前行的放置方案的做法依然可行。我们用数组s保存一行中所有的 n u m num num的放置方案,则s数组可以在预处理过程中用 D F S DFS DFS求出,同时用 c i ci ci保存第i个状态中1的个数以避免重复计算…

开始设计状态:
本题状态的维数需要增加,原因在于并不是每一行只放一个棋子,也不是每一行都要求有棋子,原先的表示方法已经无法完整表达一个状态。
我们用 f i , j , k fi,j,k fi,j,k表示第i行的状态为sj且前i行总共放置k个棋子的方案数。沿用枚举当前行方案的做法,只要当前行的放置方案和上一行的不冲突。微观地讲,就是要两行的状态s1和s2中没有同为1的位即可,亦即 s 1 s1 s1& s 2 = 0 s2=0 s2=0

然而,虽然我们枚举了第i行的放置方案,但却不知道其上一行(第i-1行)的方案 为了解决这个问题,我们不得不连第i-1行的状态一起枚举,则可以写出递推式:
在这里插入图片描述
其中s1=0,即在当前行不放置棋子; j和p(代码中的t)是需要枚举的两个状态编号。


代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,k,num,bsy;
int f[100][1<<10][30],sum[1<<11],c[1<<11];
void dfs(int ans,int dep,int flag)
{
	if(dep>n)
	{
		sum[++num]=ans;
		c[num]=flag;
		return;
	}
	dfs(ans,dep+1,flag);
	dfs(ans+(1<<dep-1),dep+2,flag+1);
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	if(n>m) swap(n,m);
	dfs(0,1,0);
	for(int i=1;i<=num;i++)
		f[1][sum[i]][c[i]]=1;
	for(int i=2;i<=m;i++)
	{
		for(int j=1;j<=num;j++)
			for(int t=1;t<=num;t++)
			{
				if(!(sum[j]&sum[t]))
				{
					for(int p=0;p<=k;p++)
						if(p>=c[j])
							f[i][sum[j]][p]+=f[i-1][sum[t]][p-c[j]];
				}
			}
	}
	for(int i=1;i<=num;i++)
		bsy+=f[m][sum[i]][k];
	printf("%d",bsy);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值