动态规划:状态压缩DP(2)(P1896 互不侵犯 ,矩阵计数)

news2024/11/26 2:38:15

文章目录

  • 互不侵犯
  • 矩阵计数

互不侵犯

题目传送门

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

升级版的八皇后问题,但是绝不是深搜dfs可以解决的,这是个指数型复杂度的问题,我们只能使用状态压缩DP

我们可以把每一行看作一个二进制数,则我们就可以使用01规定此格子有无国王。

例如每一行为: 0101 则四个位置分别表示: 空,有国王,空,有国王

我们不妨对国王所能攻击到达的 八个位置展开讨论,只要别的国王不在这八个位置中,则就是一种方案数。

因此我们首先判断在一行中左右是否存在相邻的1的情况:我们称左右相邻的这种情况为第一性质

  1. 判断 i 的右边有没有国王,则就是判断 1的右边是不是也为1,则可以把 i 右移一位,与 i 相与,若结果为0则不是相邻的1
  2. 判断 i 的左边有没有国王,则就是判断 1的左边是不是也为1,则可以把 i 左移一位,与 i 相与,若结果为0则不是相邻的1

在这里插入图片描述

第一性质的判断我们可以通过预处理来求出:

  • cnt 数组记录每一行国王出现的数量
  • zt数组记录如果左右不相邻为1,则记录这个二进制状态是合法的,因为我们通过这种状态来往下求出下面的行。

我们不光要使得 第一性质 即行左右满足不相邻为1,还要使得行的左上右下,左下右上,正上正下都满足不相邻为1,我们称它为第二性质

第二性质如何判断? 注意我们在这时已经转移到了 两行之间的状态,所以说应该是 i 与 i-1 行,

  1. 正上正下: 只需满足 i & j 等于0 即可满足。
  2. 左上右下:只需满足 (j<<1)&1 等于0 即可。
  3. 左下右上: 只需满足 (i<<1)&j 等于0 即可。

在这里插入图片描述


如果它既满足第一性质,也满足第二性质,则我们就可以通过状态转移来转移状态:

定义dp[i] [j] [S] :表示第i行,选择j个国王的状态为S,的时候的方案数

注意:此时的S为 一个二进制数字,表示满足的同一行 第一性质的二进制数字,例如 001,010 ,100,101 都是满足的第一性质的二进制数字,因此只要我们的一行满足了,我们便可以通过枚举每一行加之第二性质的判断来使得整个棋盘都满足第一和第二性质

状态转移方程如下:
d p [ i ] [ j ] [ s 1 ] + = d p [ i − 1 ] [ j − c n t [ s 1 ] ] [ s 2 ] dp[i][j][s1] +=dp[i-1][j-cnt[s1]][s2] dp[i][j][s1]+=dp[i1][jcnt[s1]][s2]
解释:第 i 行选 j 个国王的状态 s1 的方案数的 += 第 i-1行,选 j - cnt[ s1 ] 个国王的状态 s2 的方案数

其中 cnt 记录了某一个状态的国王个数(即1的个数),通过 zt 数组的值来获得,zt数组记录状态,cnt[s1]就等于 s1状态的国王的数量


//TODO: Write code here
int n,m;
const int N=1e3+10;
int nums[N],dp[10][100][2000];//i行,j个国王,k个状态
int cnt[N],zt[N];//cnt[i]表示第i行国王的数量,zt[i]表示所有满足条件的左右不相邻的状态
int num;//所有可能的状态总数
void init()
{
    /*
    预处理行内的相邻的情况: 保证行内,左右的数字不都是1
    */
    for (int i=0;i<(1<<n);i++)
    {
        int temp=0,s=i;
        while (s)   
        {
            if (s&1) temp++;//如果最后一位是1
            s>>=1;
        }
        cnt[i]=temp; //第i行数字的二进制的国王数量
        if (((i>>1)&i)==0 && ((i<<1)&i)==0) zt[++num]=i;//左右不相邻,这一状态合法
    }
}
signed main()
{
	cin>>n>>m;
    //首先预处理行内的情况
    init();
    /*
    dp[i][j][k]表示第i行有j个国王,状态为k的方案数
    */
    //然后处理行之间的情况
    dp[0][0][0]=1;
    for (int i=1;i<=n;i++)//枚举每一行
    {
        for (int l=1;l<=num;l++)//枚举第i行的状态
        {
            int s1=zt[l];//获得第i行的状态,就是一个二进制数字:0代表空,1代表国王
            for (int k=1;k<=num;k++)//枚举第i-1行的状态
            {
                int s2=zt[k];//获得第i-1行的状态,就是一个二进制数字:0代表空,1代表国王
                //第i行与第i-1行的上下,左上右下,左下右上都不存在相邻的1
                if ((s1&s2)==0 && (s1&(s2>>1))==0 && ((s2<<1)&s1)==0)
                {
                    for (int j=0;j<=m;j++)//枚举国王的个数
                    {
                        if (j-cnt[s1]>=0)
                        {
                            dp[i][j][s1]+=dp[i-1][j-cnt[s1]][s2];
                        }
                    }
                }
            }
        }
    }
    int ans=0;
    for (int i=1;i<=num;i++)//枚举所有的状态
    {
        ans+=dp[n][m][zt[i]];//累加第n行选择m个国王的数量
    }
    cout<<ans;
#define one 1
	return 0;
}


矩阵计数

矩阵计数 - 蓝桥云课 (lanqiao.cn)

一个 �×�N×M 的方格矩阵,每一个方格中包含一个字符 O 或者字符 X。

要求矩阵中不存在连续一行 3 个 X 或者连续一列 3 个 X。

问这样的矩阵一共有多少种?


这道题目与上面的《互不侵犯》较为相似,上题让我们找到满足国王在它的周围八个方向不能存在别人的总方案数,而这道题目让我们在 一行和一列中不能连续出现 3 个 x

我们像之前一样,首先把 每一行的状态预处理出来,然后我们便可以在通过for循环枚举每一行接着往下求。

进行预处理,我们称之为 第一性质,即首先要满足一行中不能连续出现三个X,我们可以通过以下的代码解决:

  1. check传入一个参数,通过 对传入的这个参数进行移位操作,如果出现了 计数大于等于3,则代表出现了连续的 XXX,因此我们此时就不能选用这个状态,返回true,返回false才是我们需要的正确结果
  2. 在init中我们枚举的一行的最大状态为 111,最小为 000,这与 m 列数有关,不要写成 n行数
  3. 记录满足条件的状态,存储合法状态在 zt 数组中
bool check(int num)
{
    int c = 0;
    while (num)
    {
        if ((num & 1)==1) c++;
        else c = 0;
        if (c >= 3)
        {
            return true;	//出现了三种连续的1
        }
        num >>= 1;
    }
    return false;
}
void init()
{
    //预处理每行
    for (int i = 0; i <(1<<m); i++)//最大111状态  最小000状态
    {
        if (!check(i))
        {
            zt[++num] = i; //统计合法的状态个数,并存储
        }
    }
}

我们设 dp 数组为 dp[i] [j] [k] 表示以第 i 行 具有的 j 状态,第 i-1 行具有的 k 状态所表示的最大的方案数

即,我们的dp数组每次保留两行,因此我们可以通过当前行与前一行的 dp数组 来共同表示一个第三行。

状态转移方程:
d p [ i ] [ z t [ j ] ] [ z t [ k ] ] + = d p [ i − 1 ] [ z t [ k ] ] [ z t [ p ] ] dp[i][zt[j]][zt[k]]+=dp[i-1][zt[k]][zt[p]] dp[i][zt[j]][zt[k]]+=dp[i1][zt[k]][zt[p]]
我们通过一个四重循环来解决这个问题,首先枚举行数,然后枚举三个状态,只有当 三个状态的与运算为0的时候,则此时满足第二性质,意味着这一列不会有三个连续的 X,此时我们便可以利用这三个状态进行状态转移

  • 四重for循环前要做的准备:
    • 因为我们的需要枚举连续三行的状态,如果当 i =1的时候,它具有 前一行为0,但是没有前一行的前一行,因此我们需要把 dp[1] [zt[i]] [0] 初始化为0,代表这个状态默认为0方案
  • 结果的查找:
    • 寻找一个最大值,在最后一行 n中,枚举n的状态与前一行的状态

AC code

#include <bits/stdc++.h>
using namespace std;
#define int long long

//状压DP:矩阵计数
namespace test48
{
	int n, m;
	const int N = 1005;
	int dp[1<<6][1<<6][1<<6];//从i行选择j个X状态为S的方案数
	int zt[N],num;//cnt存储每一行的X的数量 zt存储这一个合法的状态
	bool check(int num)
	{
		int c = 0;
		while (num)
		{
			if ((num & 1)==1) c++;
			else c = 0;
			if (c >= 3)
      {
         return true;	//出现了三种连续的1
      }
			num >>= 1;
		}
		return false;
	}
	void init()
	{
		//预处理每行
		for (int i = 0; i <(1<<m); i++)//最大111状态  最小000状态
		{
			if (!check(i))
			{
				zt[++num] = i; //统计合法的状态个数,并存储
			}
		}
	}
	void test()
	{
		cin >> n >> m;
		//第一性质:左右不相邻
		init();

		for (int i = 1; i <= num; i++)
		{
			dp[1][zt[i]][0] = 1;
		}
		for (int i = 1; i <= n; i++)
		{
			for (int p = 1; p <= num; p++)//枚举第i行
			{
				int s1 = zt[p];
				for (int l = 1; l <= num; l++)//枚举第i-1行
				{
					int s2 = zt[l];
					for (int r = 1; r <= num; r++)//枚举第i-2行
					{
						int s3 = zt[r];
						//如果正上与正下不相邻,则满足第二性质
						if ((s1 & s2 & s3) == 0)//三行不同时为1则是一个合法状态
						{
							//第i行的合法状态为s1,前一行的合法状态为s2 --> 
							//第i-1行的合法状态为s2,第i-2行的合法状态为s3
							dp[i][s1][s2] += dp[i - 1][s2][s3];
						}
					}
				}
			}
		}
		int ans = 0;
		for (int i = 1; i <= num; i++)
		{
			for (int j = 1; j <= num; j++)
			{
				ans += dp[n][zt[i]][zt[j]];
			}
		}
		cout << ans;
	}
}
signed main()
{
	using namespace test48;
	test48::test();
	return 0;
}

态为s3
dp[i][s1][s2] += dp[i - 1][s2][s3];
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= num; i++)
{
for (int j = 1; j <= num; j++)
{
ans += dp[n][zt[i]][zt[j]];
}
}
cout << ans;
}
}
signed main()
{
using namespace test48;
test48::test();
return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/336473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(一)随处可见的LED广告屏是怎么工作的呢?

笔者前段时间项目上正好用到一块32*128分辨率的单色LED&#xff0c;正式介入开发的时候&#xff0c;才发现网上的资料少之又少&#xff0c;供应商也给不出有价值的参考信息。故打算分成三篇详细的介绍下开发过程中遇到的问题。本篇主要介绍LED屏幕的一些基础信息&#xff0c;第…

C#互联网医院源码 智慧医院小程序源码 在线问诊在线开方源码

互联网医院平台源码 智慧医院管理系统源码 开发环境&#xff1a;ASP.NET C# VS2019 SQL2008 依托于实体医院利用互联网技术对接院内业务信息系统&#xff0c;向患者提供基于线上问诊、预约挂号、缴费结算、医患互动、诊后随访、健康科普和复诊等全面的医疗健康互联网服务。…

关于apifox和postman接口工具我有话要说~~

Apifox 和 Postman 都是流行的接口测试工具&#xff0c;各自有其优势和缺点。Apifox 的优势在于它提供了强大的可视化界面&#xff0c;可以方便地测试和监控 API。它还支持多种请求方式&#xff0c;并且支持对请求和响应进行代码生成。但是&#xff0c;Apifox 的缺点在于它不太…

详解指针(1)(初阶版)

前言&#xff1a;本篇是详解指针&#xff08;1&#xff09;&#xff0c;内容包括&#xff1a;指针是什么&#xff0c;指针和指针类型&#xff0c;野指针 part 1&#xff1a;指针是什么 1 指针就是地址&#xff0c;口语中说的指针通常指的是指针变量 2 指针变量&#xff1a;存…

shiro721——CVE-2019-12422

这两个漏洞主要区别在于Shiro550使⽤已知密钥碰撞&#xff0c;后者Shiro721是使⽤ 登录后rememberMe {value}去爆破正确的key值 进⽽反序列化&#xff0c;对⽐Shiro550条件只要有 ⾜够密钥库 &#xff08;条件⽐较低&#xff09;、Shiro721需要登录&#xff08;要求⽐较⾼鸡肋 …

Unity基于GraphView的行为树编辑器

这里写自定义目录标题概述基于GitHub上&#xff1a;目前这只是做了一些比较基础的功能节点开发&#xff0c;仅仅用于学习交流&#xff0c;非完成品。项目GitHub连接&#xff1a;[https://github.com/HengyuanLee/BehaviorTreeExamples](https://github.com/HengyuanLee/Behavio…

GNSS 精密钟差产品介绍与DCB改正详解

文章目录前言参考前言 IGS 提供的 GNSS 轨道产品和钟差产品的解算基准并非完全一样, 对于精密产品&#xff0c;各 GNSS 系统的参考基准均为双频无电离层组合&#xff1b;对于广播星历&#xff0c;则区分 GPS 类卫星(GPS,Galileo,QZSS) 基于双频无电离组合的伪距以及 BDS 卫星系…

CAPL(vTESTStudio) - DoIP - TCP发送_05

TCP发送 参数定义 版本号:02 FD or 01 FE or 其他任意值数据类型:00 05 or 00 06 or 80 01 or其他任意值数据长度:想要发送的任意长度

Node.js http 模块详解:request 对象

前言 前文介绍了 http 模块的基本用法&#xff0c;主要就是调用 createServer 和 listen 方法来创建和启动服务。要处理具体的 HTTP 请求&#xff0c;就要在 createServer 方法中写点什么。本文来介绍处理请求的两个核心对象之一的 request 。 URL HTTP 协议最早设计出来&am…

车载TBOX嵌入式设备软件的性能测试

作者 | 李伟 上海控安安全测评中心安全测评部总监 来源 | 鉴源实验室 01 ECU软件和通用软件性能测试的区别 通用软件进行性能测试时通常会通过压力测试、负载测试、稳定性测试、疲劳强度测试、用户并发访问测试等等方法来了解当前软件系统的各项性能指标数据&#xff0c;并在…

【mybatis】实现分页查询

一 .使用原生分页器的实体类 1.1 java代码部分 方法多 不易书写 package cn.bdqn.entity;public class Page {private Integer pageIndex;//页码private Integer pageSize;//页大小 显示多少行数据private Integer totalCounts;//数据的总行数private Integer totalPages;//…

docker安装部署dragonfly2镜像加速服务

Dragonfly安装部署文档 ​ Dragonfly 作为龙蜥社区的镜像加速标准解决方案&#xff0c;是一款基于 P2P 的智能镜像和文件分发工具。它旨在提高大规模文件传输的效率和速率&#xff0c;最大限度地利用网络带宽。在应用分发、缓存分发、日志分发和镜像分发等领域被大规模使用。 …

鸟哥的Linux私房菜读书笔记:Linux磁盘与文件系统管理

系统管理员很重要的任务之一就是管理好自己的磁盘文件系统&#xff0c; 每个分区不可太大也不能太小&#xff0c; 太大会造成磁盘容量的浪费&#xff0c; 太小则会产生文件无法储存的困扰。 前面谈到的文件权限与属性中&#xff0c; 这些权限与属性分别记录在文件系统的哪个区块…

[Golang实战]如何快速接入chatgpt/openai?[引入go-gpt3][新手开箱可用]

如何快速接入chatgpt?[引入go-gpt3]上文介绍了如何在网页使用chatgpt?V1.介绍下在golang中使用chatgpt?1.查看官网推荐的chatgpt项目2.访问go-gpt33.使用并运行在自己的项目中...(是因为例子很难理解,所以一一对应了属性做了配置)3.1安装项目3.2换上自己的代码3.3换上自己的…

LeetCode - 1109 - 航班预定统计

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 1109. 航班预订统计 - 力扣&#xff08;LeetCode&#xff09; 题目描述 这里有 n 个航班&#xff0c;它们分别从 1 到 n 进行编号。 有一份航班预订表 bookings &#xff0c;表中第 i 条预订记录 bookin…

【C语言】BCD码、十进制互相转换

目录 0. 前言&#xff1a; 1. BCD码 2. 算法原理 3. 进制转换 3.1 两位BCD码的转换&#xff1a; 3.2 其他进制转换 3.3 任意进制转二进制 参考资料&#xff1a; 0. 前言&#xff1a; 记录今天用15单片机写DS1302时钟芯片程序的时候遇到的问题 时间显示是这个样的 0 1…

电源浪涌保护器的演化发展与未来创新方向

随着科学信息技术和城市经济的发展&#xff0c;感应雷、电磁脉冲和雷电波浸入所造成的危害大大增加。一般建筑物的外部防雷措施只能预防直击雷&#xff0c;而强大的电磁场产生的感应雷和脉冲电压却能潜入室内危及电路上的设备。如今通讯网络、电脑仪器和各类用电设备越来越精密…

2023VR视频加密解决方案

如今VR技术在各个领域中的使用&#xff0c;使得我们在日常生活中也可以看到它的身影&#xff0c;常见的就是应用到培训、影院和游戏当中&#xff0c;我们都知道VR视频比传统的平面视频能给用户带来更好的体验&#xff0c;而且现在在教育、娱乐等领域VR类视频也越来越多。相比于…

运动基元(二):贝塞尔曲线

贝塞尔曲线是我第一个深入接触并使用于路径规划的运动基元。N阶贝塞尔曲线具有很多优良的特性,例如端点性、N阶可导性、对称性、曲率连续性、凸包性、几何不变性、仿射不变性以及变差缩减性。本章主要介绍贝塞尔曲线用于运动基元时几个特别有用的特性。 一、贝塞尔曲线的定义 …

C++复习笔记4

编译器给一个类默认生成的六个成员函数&#xff1a; 构造函数、析构函数、拷贝构造函数、赋值运算符重载、对象取地址运算符以及常对象取地址运算符。其中析构函数不能重载&#xff0c;默认的拷贝和赋值两个函数存在浅拷贝的问题。 对象取地址运算符重载和常对象取地址运算符…