[AHOI2009]中国象棋

本文探讨了一个棋盘放置问题,即在n*m的棋盘上放置棋子,要求每一行每一列最多放置2个棋子,求所有放置方案的总数。文章提供了三种解题思路:暴力搜索、动态规划结合状态压缩及优化后的动态规划方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

给出一个n*m的棋盘,让你在格点上放棋子,每一行每一列最多放2个棋子,放棋子的总数数不限(可以为0),求方案数模9999973的结果。

100%的数据中NM均不超过100 

50%的数据中NM至少有一个数不超过8

30%的数据中NM均不超过6 

30%做法:

6*6的棋盘上,一共最多放12个棋子,暴力搜素加点剪枝即可。

50%做法:

我们意识到有一维最多为8,这意味着什么呢?我们可以对这一维进行状压,设一个3进制的8位数,第i位上的数表示这一维的第i个位置上已经放了多少个棋子。假设行不超过8,设fi】【j】表示dp到第i列,行的状况是3进制数中的j,然后考虑用上一行的值进行转移,在这一行不放棋子有1种,放一个棋子有8种,放2个棋子有28种,每一行一共37种可能合法方案。时间复杂度:

对于50%的数据来说时间复杂度最多2400万左右,怎么都不会超。

100%做法:

50%做法的基础上我们继续思考,发现每一行每一列能放的棋子数量极少,最多只能放两个,也就是说当我们考虑一行一行的放棋子的时候,每一列只会有3种状态,还没在这列上放过棋子,放了1个棋子以及放了2个棋子。那么,我们转移的时候真的有必要记录下每一列的状态吗?答案是否定的,我们只需要知道这3种状态每一种状态的列的数量,然后对每一种状态进行整体考虑就可以了。那么,dp的思路就出来了,设fi】【j】【k】为考虑到第i行,这前i行中有j列还没有放过棋子,有k列放了1个棋子,那么用列的总数减去(j+k)就可以知道有多少列放了2个棋子了,接下来考虑fi】【j】【k】会对那些值产生贡献:

1.下一行一个棋子都不放

2.下一行在一个原本没有棋子的列放了1个棋子

3.下一行在一个原本有1个棋子的列放了1个棋子

4.2个原本没有棋子的列各放一个棋子

5.2个原本有1个棋子的列各放一个棋子

6.分别在1个原本没棋子的列和原本有一个棋子的列各放一个棋子

                                   

                                                                推荐番:《约会大作战》

处理好边界问题,这道题就大功告成了!

100分代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
long long n,m,ans,f[105][205][205];
int main()
{
	cin>>n>>m;
	f[1][m][0]=1;f[1][m-1][1]=m;if (m>=2)f[1][m-2][2]=m*(m-1)/2;
	for (int i=2;i<=n;i++)
	{
		for (int j=0;j<=m;j++)
		{
			for (int k=0;k<=m-j;k++)
			{
				f[i][j][k]+=f[i-1][j][k];
			    if (k>=1)f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k-1]*(j+1))%9999973;
			    f[i][j][k]=(f[i][j][k]+f[i-1][j][k+1]*(k+1))%9999973;
			    if (k>=2)f[i][j][k]=(f[i][j][k]+f[i-1][j+2][k-2]*(j+2)*(j+1)/2)%9999973;
			    f[i][j][k]=(f[i][j][k]+f[i-1][j][k+2]*(k+2)*(k+1)/2)%9999973;
			    f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k]*(j+1)*k)%9999973;
			}
		}
	}
	ans=0;
	for (int i=0;i<=m;i++)
	{
		for (int j=0;j<=m;j++)
		ans=(ans+f[n][i][j])%9999973;
	}
	cout<<ans;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值