Luogu P8756 [蓝桥杯 2021 省 AB2] 国际象棋 题解

Description

给定一个 \(n\times m\) 的棋盘,求在这个棋盘上放 \(k\) 个马的方案数。这里的马是国际象棋的马。

其中 \(n\le6,m\le100,k\le20\)

Solution

注意到 \(n\) 的范围只有 \(6\),显然可以状态压缩动态规划。我们用一个二进制数 \(x\) 表示第 \(i\) 行的状态,\(x\) 的从左到右第 \(j\) 位为 \(1\) 表示在第 \(i\) 行第 \(j\) 列放了马,为 \(0\) 表示不放。可是 \(m\) 的范围达到了 \(100\),每一行的状态数高达 \(2^{100}\) 种,显然不能这样压缩状态,但是按列来压缩状态又过于繁琐。注意到在 \(n\times m\) 的棋盘上的方案数和在 \(m\times n\) 的棋盘上的方案数是相等的,所以可以先将 \(n,m\) 互换。这样一来每一行的状态就只有 \(2^6\) 种了。

假设我们当前 dp 到第 \(t\) 行,第 \(t\) 行某个位置能不能放马和第 \(t-1\)\(t-2\) 行的状态都有关。第 \(t\) 行能放的马的数量和前面 \(t-1\) 行已经放的马的数量有关。于是就可以设计出状态了:设 \(f_{t,i,j,k}\) 表示当前在第 \(t\) 行,前 \(t\) 行总共放置了 \(i\) 个马,第 \(t\) 行状态为 \(j\),第 \(t-1\) 行状态为 \(k\) 时的总方案数。设 \(g(x)\) 表示 \(x\) 的二进制表示中 \(1\) 的个数。状态转移方程:

\[f_{t,i,j,k}=\sum\limits_{k_1=0}^{2^m-1}f_{t-1,i-g(j),k,k_1} \]

那么如何判断某两个状态之间能否转移呢?首先显然有第 \(i,i-1,i-2\) 行放的马的总数不超过前 \(i\) 行放的马的总数。其次,需要满足每一行之间马和马不能互相攻击。

具体而言,可以这样写:

bool check(int i,int j,int k,int k1)
{
	if(g(j)+g(k)+g(k1)>i) return false;
	if((j<<1)&k1) return false;//第t行和第t-2行
	if((j>>1)&k1) return false;//第t行和第t-2行
	if((j>>2)&k) return false;//第t行和第t-1行
	if((j<<2)&k) return false;//第t行和第t-1行
	if((k>>2)&k1) return false;//第t-1行和第t-2行
	if((k<<2)&k1) return false;//第t-1行和第t-2行
	return true;
}

这几条判断语句根据题目中给出的国际象棋马的走法很好理解,这里就不赘述了。要注意这里的 \(j,k,k_1\) 我们都是站在二进制的角度去看的。最终的答案就是:

\[\sum\limits_{i=0}^{2^m-1}\sum\limits_{j=0}^{2^m-1}f_{n,k,i,j} \]

由于对于不合法的状态,方案数一定为为 \(0\),因此最后统计答案时直接相加即可。

时间复杂度 \(O(2^{3n}mk)\),但由于有很多不合法状态,所以实际远远达不到这个数。

Code

#include<bits/stdc++.h>
#define lowbit(x) x&-x
using namespace std;
const int N=101,MAXK=21,M=1<<6;
const int mod=1e9+7;
int f[N][MAXK][M][M],g[M];
int pop_cnt(int x)
{
	int res=0;
	while(x) res++,x-=lowbit(x);
	return res;
}
int main()
{
	int n,m,K;
	scanf("%d%d%d",&n,&m,&K);
	swap(n,m);
	for(int i=0;i<=(1<<m)-1;i++) g[i]=pop_cnt(i);
	for(int i=0;i<=(1<<m)-1;i++) f[1][g[i]][i][0]=1;
	for(int t=2;t<=n;t++)
	{
		for(int i=0;i<=K;i++)
		{
			for(int j=0;j<=(1<<m)-1;j++)
			{
				if(g[j]>i) continue;
				for(int k=0;k<=(1<<m)-1;k++)
				{
					if(g[j]+g[k]>i||((j<<2)&k)||((j>>2)&k)) continue;
					for(int k1=0;k1<=(1<<m)-1;k1++)
					{
						if(g[k]+g[k1]>i-g[j]) continue;
						if(((k<<2)&k1)||((k>>2)&k1)) continue;
						if(((j<<1)&k1)||((j>>1)&k1)) continue;
						f[t][i][j][k]=(f[t][i][j][k]+f[t-1][i-g[j]][k][k1])%mod;
					}
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<=(1<<m)-1;i++) for(int j=0;j<=(1<<m)-1;j++) ans=(ans+f[n][K][i][j])%mod;
	printf("%d\n",ans);
	return 0;
}
posted @   __Star_Sky  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示