计数 dp 专题 1

news/2025/3/13 18:49:03/文章来源:https://www.cnblogs.com/XP3301Pipi/p/18770623

计数 dp 专题 1

Problem A. P5074 Eat the Trees

Description

给出 \(n \times m\) 的方格,有些格子不能铺线,其它格子必须铺,可以形成多个闭合回路。问有多少种铺法?

\((2 \le n,m \le 12)\)

Solution

首先考虑如何判定一个铺线的方案合法。

我们把每一个格子看成一个点,线看成节点之间的边。那么一种铺线方案合法,当且仅当每个标记为 \(1\) 的节点的度数都是 \(2\),每个标记为 \(0\) 的节点的度数都是 \(0\)

我们设函数 \(g(E) \to \{0,1\}\),来判断 \(E\) 这张图是否合法。我们接下来要尽可能减少 \(g\) 中需要保留的信息,以保证接下来的 dp 复杂度不会爆炸。

如果我们在 \(g\) 中保存下所有的边,那么我们的时间复杂度为 \(O(2^{nm}nm)\approx 2.4\times 10^9\),无法接受。

我们从上到下一行一行地检查每一个点,那么我们不必一次性读入所有的边,每检查到一个 \((i,j)\) 再把边 \([(i,j),(i+1,j)]\)\([(i,j),(i,j+1)]\) 读入即可。进一步地,我们可以发现,对于一条边,如果它的两个端点都被检查过了,那么这条边就没有用了。

假如我们接下来要检查的点是 \((i,j)\)。可以发现,当前时刻仍需要保留的边有:

  1. \([(i,k),(i+1,k)],1\leq k< j\)
  2. \([(i-1,k),(i,k)],j\leq k \leq m\)
  3. \([(i,j-1),(i,j)]\)

画图可知,这些边形成了一个类似于 “轮廓线” 的形状。

然后删除 \([(i,j-1),(i,j)]\)\([(i-1,j),(i,j)]\) 两条边,读入 \([(i,j),(i,j+1)]\)\([(i,j),(i+1,j)]\) 两条边。

这样,我们在任意时刻,都保证了保留的边的数量 \(\leq m+1\)

那么我们就可以进行状压 dp 了。设 \(f_{i,j,S}\) 表示已经检查完了 \((i,j)\) 点,要保存的边的选取情况为 \(S\),检查过的点都合法的方案数。转移枚举 \((i,j)\) 的两条新边选不选即可。

对于单组数据,时间复杂度为 \(O(2^{m}nm)\)

int T,n,m,a[N][N],AS;
ll f[N][N][M];void Solve(){memset(f,0,sizeof(f));f[1][0][0]=1; AS=(1<<(m+1))-1;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int s=0;s<=AS;s++){if(!f[i][j-1][s]) continue;int p=(s>>(j-1))&1,q=(s>>j&1);int tx=(i<n)&(a[i][j]);int ty=(j<m)&(a[i][j]);for(int x=0;x<=tx;x++){for(int y=0;y<=ty;y++){if((p+q+x+y!=2)&&a[i][j]) continue;if((p+q+x+y!=0)&&(!a[i][j])) continue;int t=s^((x!=p)*(1<<(j-1)))^((y!=q)*(1<<j));f[i][j][t]+=f[i][j-1][s];}}}for(int s=0;s<=AS;s++){if(!f[i][j][s]) continue;if(j==m) f[i+1][0][s<<1]+=f[i][j][s];}}}ll ans=0;for(int s=0;s<=AS;s++) ans+=f[n][m][s];printf("%lld\n",ans);
}signed main(){read(T);while(T--){read(n),read(m);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)read(a[i][j]);}Solve();}return 0;
}

Problem B. CF1229E2 Marek and Matching

Descreption

给定一张 \(2n\) 个点的二分图,\(l_i,r_j\) 之间有边的概率为 \(p_{i,j}\),求这张图有完美匹配的概率。

\(1 \leq n \leq 7\)

Solution

非常暴力的一个数据范围。

我们还是设函数 \(g(E)\to \{0,1\}\),来判断一张二分图是否有完美匹配。

由于数据范围非常小,我们可以设 \(f_{i,S}\) 表示考虑了左边的 \(1\sim i\) 号点,能否匹配右边的 \(S\)。转移显然。

进一步地,我们发现有用的 \(f_{i,S}\) 一定满足 \(|S|=i\)。那么可以直接扔掉第一维,只需要记 \(2^n\) 个 bit 了。

于是我们可以设另一个 dp:\(F_{i,f'}\) 表示考虑了 \(1\sim i\) 号点,使得当前的 \(f\) 数组为 \(f'\) 的概率。

考虑枚举 \(i+1\) 号点连向的点集 \(T\),首先处理出新得到的 \(f\) 数组,然后乘上相应的概率转移。但这样每个 \(T\) 都要做一遍,效率未免太低。

我们首先对于右边每个点 \(i\) 处理出一个 \(G_i\),表示 \(f_{S\setminus\{i\}}=1\)\(S\) 的集合。

那么对于一个 \(T\),新的 \(f\) 中有值的位置就是 \(\bigcup_{i=1}^n G_i\) 中的元素。

这样直接做,状态数是 \(O(n2^{2^n})\) 的,转移复杂度为 \(O(2^nn)\) 的,总复杂度 \(O(2^{2^n+n}n^2)\),不可接受。

可以发现,对于每一个 \(i\)\(f\) 中有值的位置只有 \(\binom{n}{i}\) 个,总复杂度降为 \(O(2^{\binom{n}{i}}n^2)\)。可以通过 Easy Version。

再进一步,直觉告诉我们,\(F\) 中有很多的状态是无法到达的。打表发现有效的状态只有几万个,记忆化搜索即可。可以通过 Hard Version。

const ll mod=1e9+7;int n,p[N][N],AS,id[M];
ll e[N][N],INV,h[N][M];
vector<int> num[N];unordered_map<ll,ll> f[N];ll QuickPow(ll x,ll y){ll res=1;while(y){if(y&1) res=res*x%mod;x=x*x%mod; y>>=1;}return res;
}inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}inline void Add(ll &x,ll y){x=Mod(x+y);}ll dfs(int x,ll y){if(x==n) return y==1; if(f[x].count(y)) return f[x][y];ll G[N]; memset(G,0,sizeof(G));for(int s:num[x]){if(!(y>>id[s]&1)) continue;for(int i=0;i<n;i++)if(!(s>>i&1)) G[i]|=(1ll<<id[s|(1<<i)]);}ll res=0;for(int s=0;s<=AS;s++){ll v=0;for(int i=0;i<n;i++)if(s>>i&1) v|=G[i];Add(res,dfs(x+1,v)*h[x][s]%mod);}return f[x][y]=res;
}signed main(){read(n); INV=QuickPow(100,mod-2);for(int i=0;i<n;i++){for(int j=0;j<n;j++){read(p[i][j]);e[i][j]=p[i][j]*INV%mod;}}AS=(1<<n)-1;for(int i=0;i<n;i++){for(int s=0;s<=AS;s++){h[i][s]=1;for(int j=0;j<n;j++){if(s>>j&1) (h[i][s]*=e[i][j])%=mod;else (h[i][s]*=Mod(1-e[i][j]+mod))%=mod;}}}for(int i=0;i<=AS;i++){int x=__builtin_popcount(i);num[x].push_back(i);id[i]=(signed)num[x].size()-1;}ll ans=dfs(0,1);printf("%lld\n",ans);return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python的基本运用(3)——索引、切片、字符串

一、索引 索引在公司中一般也叫下标,或角标定义:可我们可以直接使用索引来访问序列中的元素,同时索引可分为正向索引和负向索引两种,而切片也会用到索引,如下图:Python中有序列:字符,列表,元组无序:集合正向索引:从0开始负向索引:-1开始二、切片 定义:切片是指对操…

[算法学习记录] 并查集(附例题)

并查集简介 并查集是一种重要的数据结构,主要用于实现节点之间的合并查询操作(例如判断两个节点是否属于同一个连通块(共享同一个父节点的节点组成的集合叫连通块)),在解决不相交集合时有很大的用处;并查集同样常用于处理无向图,来描述接点的连通性,在初始化时,每个节…

Electron 进程间通信(IPC)方法详解

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,它是基于 Chromium 和 Node.js 构建的,而 Chromium 本身是采用多进程架构的,所以 Electron 也是多进程的。 Electron 是一个多进程框架,它的进程主要分为两类:主进程(Main Process) 和 渲染进程(R…

从零开始的web前端学习-JavaScript

JavaScript 是一种运行在客户端(浏览器)的编程语言,实现人机互动效果:网页特效(监听用户的某些行为并令网页进行反馈) 表单验证(针对表单数据的合法性进行判断) 数据交互(获取后台数据并渲染到前端)JavaScript 组成ECMAScript:基础语法核心 Web APIs:DOM(页面文档…

【Azure Service Bus】分享使用 Python Service Bus SDK 输出SDK内操作日志

问题描述 使用Python代码消费Service Bus中的消息,默认情况 Console 中的信息都是通过 print 打印输出。 有时候需要调查更多SDK中的日志,那么如何才能让SDK输出更多的日志呢?问题解答 方法就是引入 Logging SDK,然后再初始化 ServiceBusClient 对象时,设置logging_enabl…

nvm和nodejs安装

nvm和nodejs安装安装 nvm 全名 node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。首先下载安装包,可以用GitHub上的,可以有点看,也可以用一些镜像,然后点击安装一直下一步即可。 然后打开命令行,可以用nvm -v指令…

dify文件上传到http节点

dify系统上传sys.files变量是Array[File]类型,由于 HTTP 请求节点不支持 Array[File] 上传,需要单独处理每个文件,以下是实现此功能的步骤: 添加迭代节点 迭代输入选sys.files 输出选http请求body http请求body类型选form-data 键值选迭代的item.File 本文使用dify版本为0.…

可行性分析(第五组)

目录 第1章 系统分析 1.1 可行性分析 1.1.1 技术可行性分析 1.1.2 经济可行性分析 1.1.3 社会可行性分析 1.1.4 法律可行性分析 1.2 系统流程分析 1.2.1 系统开发总流程 1.2.2 登录流程 1.2.3 系统操作流程 1.2.4 系统性能分析 第1章 可行性分析 1.1可行性分析 下面分别从技术可…

C# 子窗体中调用父窗体中的方法(或多窗体之间方法调用)

看似一个简单的功能需求,其实很多初学者处理不好的,很多朋友会这么写:C# Code://父窗体是是frmParent,子窗体是frmChildA //在父窗体中打开子窗体 frmChildA child = new frmChildA(); child.MdiParent = this; child.Show();//子窗体调父窗体方法: //错误的调用!!!!!!!! …

Qt HTTP模块——调用API对话DeepSeek

HTTP模块 Qt的网络模块(QtNetwork)支持HTTP/HTTPS协议,提供异步、非阻塞的API,实现客户端与服务器之间的 HTTP 请求与响应交互。核心类:QNetworkAccessManager:负责协调网络操作(如GET/POST请求),管理请求队列和返回的响应。 QNetworkRequest:封装HTTP请求的详细信息…

Linux下环境变量

Linux打印环境变量: echo $PATH