首页 > 其他分享 >Luogu P5005 中国象棋 - 摆上马 / Luogu P8756 国际象棋 题解 [ 蓝 ] [ 状压 dp ] [ 位运算 ]

Luogu P5005 中国象棋 - 摆上马 / Luogu P8756 国际象棋 题解 [ 蓝 ] [ 状压 dp ] [ 位运算 ]

时间:2024-08-02 17:19:34浏览次数:17  
标签:状态 中国象棋 int Luogu ll 70 题解 马脚 dp

国际象棋:模板棋盘状压。
摆上马:需要点思维的棋盘状压,相比上一道题加了“蹩马脚”的设定。

Easy_version :国际象棋

概述一下此类棋盘问题的思路:

  1. 用二进制数表示出棋盘上某一行的状态。
  2. 用位运算预处理出合法的单行状态,以及需要用到的一些东西。
  3. 用位运算判断前一行或者前几行能否转移过来。
  4. 转移前一行或者前几行的兼容类。

基础位运算

判断两边是否有:i&(i>>1)i&(i<<1)
判断 \(i\) 是否包含在合法状态 \(j\) 里: (i&j)==i

对于本题

记录下两行的状态,定义 \(dp[i][j][k][l]\) 表示第 \(i\) 行时,第 \(i\)行状态为 \(j\) ,第 \(i-1\) 行状态为 \(k\) ,且目前摆了 \(l\) 个马的方案数。

初始化 \(dp[0][0][0][0]=1\) 。

循环顺序是:

  1. 行数。
  2. 马的个数。
  3. 第 \(i\) 行状态。
  4. 第 \(i-1\) 行状态。
  5. 第 \(i-2\) 行状态。

注意由于只会用到前一行的状态,所以我们可以强行滚动数组优化,通过 \(\bmod 2\) 来解决。滚动数组每次必须先初始化为 \(0\) 。

时间复杂度 \(O(2^{3n}mk)\),有点紧,常数不能太大。

为了不在最后统计一遍,太过麻烦,所以我们多加了两行,并强制这两行必须不能放马,这样就不用手工统计方案数了。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int n,m,k,tot[70];
const ll mod=1e9+7;
ll dp[2][70][70][25];
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>k;
	for(int i=0;i<(1<<n);i++)
	{
		for(int j=0;j<n;j++)
		{
			tot[i]+=((i>>j)&1);
		}
	}
	dp[0][0][0][0]=1;
	for(int i=1;i<=m+2;i++)
	{
		for(int j=0;j<=k;j++)
		{
			for(int a=0;a<(1<<n);a++)
			{
				for(int b=0;b<(1<<n);b++)
				{
					dp[i&1][a][b][j]=0;
					if(((a>>2)&b)||((a<<2)&b))continue;
					for(int c=0;c<(1<<n);c++)
					{
						if(((a>>1)&c)||((a<<1)&c))continue;
						if(tot[a]<=j)
						{
							dp[i&1][a][b][j]=(dp[i&1][a][b][j]+dp[(i&1)^1][b][c][j-tot[a]])%mod;
						}
					}
				}
			}
		}
	}
	cout<<dp[(m+2)&1][0][0][k];
	return 0;
}

Hard_version :中国象棋 - 摆上马

相比上一道,这道必须考虑蹩马脚的情况,且不用记录马的个数。

这题难点便是在判断蹩马脚:

进阶位运算

筛选出本行左边是 \(0\) ,这一位是 \(1\) 的点: i&((~i)>>1)
筛选出本行右边是 \(0\) ,这一位是 \(1\) 的点: i&((~i)<<1)

考虑 \(i\) 和 \(i-1\) 行时

\(a\) 为第 \(i\) 行状态,\(b\) 为第 \(i-1\) 行状态,\(c\) 为第 \(i-2\) 行状态。

第 \(i\) 行左边没有蹩马脚,并且可以攻击到左上的马的情况:a&((~a)>>1)&(b>>2)
第 \(i\) 行右边没有蹩马脚,并且可以攻击到右上的马的情况:a&((~a)<<1)&(b<<2)

第 \(i-1\) 行左边没有蹩马脚,并且可以攻击到左下的马的情况:b&((~b)>>1)&(a>>2)
第 \(i-1\) 行右边没有蹩马脚,并且可以攻击到右下的马的情况:b&((~b)<<1)&(a<<2)

考虑 \(i\) 和 \(i-2\) 行时

第 \(i\) 行上边没有蹩马脚,并且可以攻击到左上的马的情况:a&(~b)&(c>>1)
第 \(i\) 行上边没有蹩马脚,并且可以攻击到右上的马的情况:a&(~b)&(c<<1)

第 \(i-2\) 行下边没有蹩马脚,并且可以攻击到左下的马的情况:c&(~b)&(a>>1)
第 \(i-2\) 行下边没有蹩马脚,并且可以攻击到右下的马的情况:c&(~b)&(a<<1)

时间复杂度 \(O(2^{3y}x)\) 。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int n,m;
const ll mod=1e9+7;
ll dp[2][70][70];
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>m>>n;
	dp[0][0][0]=1;
	for(int i=1;i<=m+2;i++)
	{
		for(int a=0;a<(1<<n);a++)
		{
			for(int b=0;b<(1<<n);b++)
			{
				dp[i&1][a][b]=0;
				if((a&((~a)>>1)&(b>>2))||((a&((~a)<<1)&(b<<2)))||(b&((~b)>>1)&(a>>2))||(b&((~b)<<1)&(a<<2)))continue;
				for(int c=0;c<(1<<n);c++)
				{
					if(((~b)&a&(c>>1))||((~b)&a&(c<<1))||((~b)&c&(a<<1))||((~b)&c&(a>>1)))continue;
					dp[i&1][a][b]=(dp[i&1][a][b]+dp[(i&1)^1][b][c])%mod;
				}
			}
		}
	}
	cout<<dp[(m+2)&1][0][0];
	return 0;
}

标签:状态,中国象棋,int,Luogu,ll,70,题解,马脚,dp
From: https://www.cnblogs.com/zhr0102/p/18339185

相关文章

  • [lnsyoj2234/luoguP9323]玩具设计
    题意原题链接给定一个\(n\)个点的无向图,你可以进行不超过\(max\_ops\)次查询操作\(\text{Connected}(x,a,b)\),每次操作可以查询在版本\(x\)中,\(a,b\)两个点之间是否连通,若连通,则返回当前版本号,否则新建一个版本,在该新版本中将这两个点之间连一条边,并返回新版本的版本号......
  • Luogu P3959 宝藏 题解 [ 紫 ] [ 状压 dp ] [ 二项式定理 ]
    宝藏:一个对着蓝书代码调都能调两个小时的大毒瘤,但是思路还是很值得借鉴的,有普通状压和三进制状压两种做法,或者暴搜剪枝也可以(这里不介绍暴搜剪枝做法)。普通状压做法观察到\(n\le12\),首先想到状压。但考虑到普通的状压不太行,因为\(K\)这个数算在代价里,会导致这个dp有后效......
  • BZOJ2839/LG10596 集合计数 题解(二项式反演+扩展欧拉定理)
    题目大意:一个有\(N\)个元素的集合有\(2^N\)个不同子集(包含空集),现在要在这\(2^N\)个集合中取出若干集合(至少一个),使得它们的交集的元素个数为\(K\),求取法的方案数,答案模\(10^9+7\)。为表述方便,不妨设这\(i\)个元素分别为\(1\simn\)。前置知识:二项式反演。考虑设\(g(......
  • 题解:CF1537E2 Erase and Extend (Hard Version)
    CF1537E2EraseandExtend题解分析通过观察题目,可以证明结果一定是由多次前缀复制得来的。题目要求你进行删和复制的操作,与其交替着操作,不如直接先删到最优的前缀再进行复制。现在就是要找最优的前缀。从头一位一位往后遍历。用\(l\)来存储目前最优前缀的长度,第\(i\)位......
  • 题解:CF1301B Motarack's Birthday
    CF1301DTimetoRun题解思维题。分析把一个格子视作一个点,每个点的度数都是偶数,所以这是一张欧拉图。而需要走遍整个方格图,可以证明只要\(k\)不超过\(4nm-2n-2m\)就一定有解。很明显存在很多种方案,这里我用的方案是:从左上角出发,向右走\(m-1\)步到头,再向左走\(m-1\)......
  • 题解:CF634A Island Puzzle
    CF634AIslandPuzzle题解分析由于我们仅能移动\(0\),所以其它数字的相对顺序较原来应该是不变的,所以我们从环中删除\(0\)再判断相对位置即可。还有需要注意的是本题是一个环,找到末尾需要用取模操作回到开头继续比较。示例代码#include<bits/stdc++.h>usingnamespacest......
  • 题解:CF718A Efim and Strange Grade
    CF718AEfimandStrangeGrade题解算法贪心+模拟思路分析显然,要最优每一次进位就只能五入不能四舍。而且当我们五入时,要取位数最高的。比如说\(1.3535\),我们有两种进位方式,一种是进位成\(1.4\),一种是进位成\(1.354\),显然前者更优。那这道题给的次数有啥用呢?考虑一种“......
  • 题解:CF1301D Time to Run
    CF1301DTimetoRun题解思维题。分析把一个格子视作一个点,每个点的度数都是偶数,所以这是一张欧拉图。而需要走遍整个方格图,可以证明只要\(k\)不超过\(4nm-2n-2m\)就一定有解。很明显存在很多种方案,这里我用的方案是:从左上角出发,向右走\(m-1\)步到头,再向左走\(m-1\)......
  • 题解:CF771A Bear and Friendship Condition
    CF771ABearandFriendshipCondition题解算法并查集,图的基本性质分析题目意思是,一旦有一些点联通,那么这些点必须两两直接相连。换句话讲,就是图中的每个联通块都是完全图。所谓完全图,就是图中的每个点都两两相连,假设一个完全图有\(n\)个点,那么我们可以通过乘法原理算出这......
  • 题解:CF507C Guess Your Way Out!
    CF507CGuessYourWayOut!题解算法模拟思路按照左右左右的方式先往下找,每次找到一个子节点时就看此节点为根的子树是否包含目标节点,如果包含就继续往下走,不包含说明目标叶子节点在另一边的子树上,那么肯定是先需要把这边的子树遍历完才能换到另一边,所以答案直接加上这个子树......