题目按照主观难度增序排列

Luogu P1758 [NOI2009] 管道取珠

有上下两个长度分别为 \(n,m\) 的管道 \(a,b\),管道中有两种不同颜色的球用 \(A,B\) 表示。现在每次只能从管道末尾开始取球,求取完所有球每种颜色序列的方案数的平方和。

考虑转换题意,首要任务是方案数的平方怎么办。

注意到一个颜色序列 \(c\) 如果出现次数为 \(a_i\),\(a_i^2\) 就是两个独立系统取到相同颜色序列的方案数。也就是说我们可以直接统计两个人在两套管道中取到相同颜色序列的方案数,这样转移就比较简单了。

考虑设 \(f_{tot,i,j}\) 表示取 \(tot\) 个球,第一个人取了 \(i\) 个上管道的球,第二个人取了 \(j\) 个上管道的球。那么第一个人就取了 \(k-i\) 个下管道的球,第二个人就取了 \(k-j\) 个下管道的球。

转移只在颜色序列相同时发生。分讨 \(2\times2=4\) 种情况,有

\[f_{tot,i,j}=
\begin{cases}
f_{tot-1,i-1,j-1} &a_i=a_j\\
f_{tot-1,i-1,j}&a_i=b_{k-j}\\
f_{tot-1,i,j-1}&b_{k-i}=a_{j}\\
f_{tot-1,i,j}&b_{k-i}=b_{k-j}
\end{cases}
\]

注意一下边界,滚一下数组,直接转移即可。

代码实现
#include<bits/stdc++.h>
using namespace std; const int maxn = 5e2 + 10, mo = 1024523;
int n, m; char a[maxn], b[maxn];
int f[2][maxn][maxn]; int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i]; reverse(a + 1, a + n + 1);
for(int i = 1; i <= m; i++) cin >> b[i]; reverse(b + 1, b + m + 1); f[0][0][0] = 1;
for(int tot = 1; tot <= n + m; tot++) {
int u = tot & 1, v = u ^ 1; for(int i = 0; i <= n ; i++) for(int j = 0; j <= n; j++) f[u][i][j] = 0;
for(int i = max(tot - m, 0); i <= min(tot, n); i++) {
for(int j = max(tot - m, 0); j <= min(tot, n); j++) {
if(a[i] == a[j] && i && j) f[u][i][j] += f[v][i - 1][j - 1];
if(a[i] == b[tot - j] && i && tot - j) f[u][i][j] += f[v][i - 1][j];
if(b[tot - i] == a[j] && j && tot - i) f[u][i][j] += f[v][i][j - 1];
if(b[tot - i] == b[tot - j] && tot - i && tot - j) f[u][i][j] += f[v][i][j];
f[u][i][j] %= mo;
}
}
} cout << f[(n + m) & 1][n][n] << endl;
return 0;
}

Luogu P4516 [JSOI2018] 潜入行动

给定 \(n\) 个点的一棵树,要用 \(k\) 个监听装置来监听所有的点。节点上放一个监听装置可以监听除这个点外与其相邻的所有点,求合法方案数 \(\bmod1e9+7\)。

树上 DP 先从子树的角度来考虑问题。如果以 \(v\) 为根的子树都被监听了,考虑它的父亲 \(u\) 可能的情况。发现会分成 \(v\) 是否有监听装置、是否被监听和 \(u\) 是否有监听装置、是否被监听几种情况。再加上题目本身是一个树形背包,我们可以设出状态 \(f_{u,k,0/1,0/1}\) 表示以 \(u\) 为根的子树内(不含 \(u\)) 所有点被监听,用了 \(k\) 个监听装置,\(u\) 是否有监听装置和 \(u\) 是否被监听 。所以答案即为 \(f_{1,k,0,1}+f_{1,k,1,1}\)。

我们先思考 \(u\) 是否被监听对转移有什么影响,如果当前 \(u\) 没有被监听,那么之前一定也没有被监听,所以只能从没有被监听的状态转移过来;如果当前 \(u\) 被监听了,就要讨论是否是 \(v\) 装置造成的,转移会复杂一些。。这里我们可以尝试大力分讨 \(u,v\) 的可能情况:

如果 \(u\) 没有被监听且没放装置,那么 \(v\) 一定不能放监听装置,所以 \(f_{u,i+j,0,0}=f_{u,i,0,0}\times f_{v,i,0,1}\);

如果 \(u\) 没有被监听但是放了监听装置,那么 \(v\) 有没有被监听到的都成为了合法方案,且 \(v\) 不能放监听装置,所以 \(f_{u,i+j,1,0}=f_{u,i,1,0}\times (f_{v,j,0,0}+f_{v,j,0,1})\);

如果 \(u\) 被监听了但是没放装置,这时候要考虑 \(u\) 是否是因为 \(v\) 第一次被监听的。如果是,那么 \(v\) 就一定有装置且被监听,贡献的合法方案是 \(f_{u,i,0,0}\times f_{v,j,1,1}\);如果不是,那么 \(v\) 无所谓是否有装置,但必须被监听,贡献的方案数就是 \(f_{u,i,0,1}\times(f_{v,j,0,1}+f_{v,j,1,1})\)。所以总转移 \(f_{u,i+j,0,1}=f_{u,i,0,0}\times f_{v,j,1,1}+f_{u,i,0,1}\times(f_{v,j,0,1}+f_{v,j,1,1})\);

如果 \(u\) 既有装置也被监听,\(v\) 让 \(u\) 第一次被监听的贡献为 \(f_{u,i,1,0}\times(f_{v,j,1,0}+f_{v,j,1,1})\);\(v\) 不是第一次让 \(u\) 被监听,这时候 \(v\) 没有任何限制,贡献为 \(f_{u,i,1,1}\times(f_{v,j,0,0}+f_{v,j,0,1}+f_{v,j,1,0}+f_{v,j,0,1})\),总贡献即 \(f_{u,i+j,1,1}=f_{u,i,1,0}\times(f_{v,j,1,0}+f_{v,j,1,1})+f_{u,i,1,1}\times(f_{v,j,0,0}+f_{v,j,0,1}+f_{v,j,1,0}+f_{v,j,0,1})\)。

就有总转移

\[f_{u,i+j,0,0}=f_{u,i,0,0}\times f_{v,i,0,1}
\]
\[f_{u,i+j,1,0}=f_{u,i,1,0}\times (f_{v,j,0,0}+f_{v,j,0,1})
\]
\[f_{u,i+j,0,1}=f_{u,i,0,0}\times f_{v,j,1,1}+f_{u,i,0,1}\times(f_{v,j,0,1}+f_{v,j,1,1})
\]
\[f_{u,i+j,1,1}=f_{u,i,1,0}\times(f_{v,j,1,0}+f_{v,j,1,1})+f_{u,i,1,1}\times(f_{v,j,0,0}+f_{v,j,0,1}+f_{v,j,1,0}+f_{v,j,0,1})
\]

但是实际上有更加简洁的做法,如果我们不讨论具体的 \(0/1\) 取值,而是用变量来替代就可以大大简化转移。不妨设现在有状态 \(f_{u,i,p_1,q_1},f_{v,j,p_2,q_2}\),考虑应该转移到什么状态?

当前状态只要之前被监听 \(v\) 有装置就应被监听,所以监听状态表示为 \(q_1|p_2\),而装置的状态只取决于之前是否被监听,所以就是 \(p_1\)。这样我们就得到了一个简洁易懂的转移:

\[f_{u,i+j,p_1,q_1|p_2}=f_{u,i+j,p_1,q_1|p_2}+f_{u,i,p_1,q_1}+f_{v,j,p_2,q_2}
\]

可能常数略大,但简单易懂!!

代码实现
#include<bits/stdc++.h>
using namespace std; const int maxn = 1e5 + 10, maxk = 1e2 + 10, mo = 1e9 + 7;
int n, k;
vector<int> e[maxn]; int sz[maxn], f[maxn][maxk][2][2], g[maxk][2][2];
void dfs(int u, int fa) {
sz[u] = f[u][0][0][0] = f[u][1][1][0] = 1;
for(int v : e[u])
if(v != fa) {
dfs(v, u); int tot = min(sz[u] + sz[v], k);
for(int i = 0; i <= sz[u]; i++)
for(int j = 0; j <= sz[v] && i + j <= tot; j++) {
for(int p1 = 0; p1 < 2; p1++)
for(int q1 = 0; q1 < 2; q1++)
for(int p2 = 0; p2 < 2; p2++)
for(int q2 = 0; q2 < 2; q2++)
if(q2 | p1)
(g[i + j][p1][q1 | p2] += 1ll * f[u][i][p1][q1] * f[v][j][p2][q2] % mo) %= mo;
} for(int k = 0; k <= tot; k++)
for(int p = 0; p < 2; p++)
for(int q = 0; q < 2; q++)
f[u][k][p][q] = g[k][p][q];
for(int k = 0; k <= tot; k++)
g[k][0][0] = g[k][0][1] = g[k][1][0] = g[k][1][1] = 0;
sz[u] = tot;
}
} int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> k;
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
e[u].push_back(v), e[v].push_back(u);
} dfs(1, 0); cout << (f[1][k][0][1] + f[1][k][1][1]) % mo;
return 0;
}

2025.3.24 DP专题的更多相关文章

  1. 决策单调性优化dp 专题练习

    决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队 ...

  2. 状压dp专题复习

    状压dp专题复习 (有些题过于水,我直接跳了) 技巧总结 : 1.矩阵状压上一行的选择情况 \(n * 2^n\) D [BZOJ2734][HNOI2012]集合选数 蒻得不行的我觉得这是一道比较难 ...

  3. 树形dp专题总结

    树形dp专题总结 大力dp的练习与晋升 原题均可以在网址上找到 技巧总结 1.换根大法 2.状态定义应只考虑考虑影响的关系 3.数据结构与dp的合理结合(T11) 4.抽直径解决求最长链的许多类问题( ...

  4. 区间dp专题练习

    区间dp专题练习 题意 1.Equal Sum Partitions ? 这嘛东西,\(n^2\)自己写去 \[\ \] \[\ \] 2.You Are the One 感觉自己智力被吊打 \(dp ...

  5. DP专题:划分数问题

    一.这个专题有什么用 练练DP 练练组合数学 ...... 二.正题 此类问题有如下几种形态: 1. 将n划分成若干正整数之和的划分数.2. 将n划分成k个正整数之和的划分数.3. 将n划分成最大数不 ...

  6. DP专题训练之HDU 2955 Robberies

    打算专题训练下DP,做一道帖一道吧~~现在的代码风格完全变了~~大概是懒了.所以.将就着看吧~哈哈 Description The aspiring Roy the Robber has seen a ...

  7. 常规DP专题练习

    POJ2279 Mr. Young's Picture Permutations 题意 Language:Default Mr. Young's Picture Permutations Time L ...

  8. 【dp专题】NOIP真题-DP专题练习

    这里学习一下DP的正确姿势. 也为了ZJOI2019去水一下做一些准备 题解就随便写写啦. 后续还是会有专题练习和综合练习的. P1005 矩阵取数游戏 给出$n \times m$矩阵每次在每一行取 ...

  9. dp专题练习

    顺便开另外一篇放一些学过的各种dp dp总结:https://www.cnblogs.com/henry-1202/p/9194066.html 开坑先放15道题,后面慢慢补 目标50道题啦~~,目前 ...

  10. dp专题训练

    ****************************************************************************************** 动态规划 专题训练 ...

随机推荐

  1. 考拉 T_Q_X 的博客搬运(搬运)

    博客搬迁现场直播 各位观众们大家好,欢迎来到新闻透视 今天为您直播某菜鸡oier tqx 的博客搬迁现场. Q:请问tqx,您为什么要将博客从CSDN搬迁到博客园呢? tqx:懂得都懂,不懂的我也不多 ...

  2. 流程控制之for循环练习画三角形

    package com.yeyue.struct; public class TestDemo { public static void main(String[] args) { //打印三角形 5 ...

  3. nacos(四): 创建第一个消费者Conumer(单体)

    接上一篇<nacos(三): 创建第一个生产者producer(单体)>,我们这一篇实现单体的消费者功能,准备与上一次的生产者集成在一个单体项目中. 消费者的本质其实就是向nacos注册后 ...

  4. GIT如何进行团队协作

    加入其他开发成员.修改代码并提交 选择库点击管理 点击添加成员邀请用户 以下三种方式都行 加入后如下 第二人如何进行修改文件: 在文件夹中创建本地库git init 建立远程连接 git remote ...

  5. RFID实践——NET IoT程序读取高频RFID卡或者标签

    这篇文章是一份RFID实践的保姆级教程,将详细介绍如何用 Raspberry Pi 连接 PN5180 模块,并开发 .NET IoT 程序读写ISO14443 和 ISO15693协议的卡/标签. ...

  6. 收集 Spring Boot 相关的学习资料

    收集 Spring Boot 相关的学习资料,Spring Cloud点这里 重点推荐:Spring Boot 中文索引 推荐博客 纯洁的微笑-Spring Boot系列文章 林祥纤-从零开始学Spr ...

  7. Ubuntu 部署饥荒联机版服务器 Linux DST_Dedicate_Server

    0. 文件夹 - ~ |- ~/steamcmd # 装的是steamcmd_linux.tar.gz以及其解压出来的东西 |- ~/DST # 装的是DST服务器可执行文件.世界存档.世界模板 |- ...

  8. 启动U盘制作-小白保姆式超详细刷机教程

    疑难解答加微信机器人,给它发:进群,会拉你进入八米交流群 机器人微信号:bamibot 简洁版教程访问:https://bbs.8miyun.cn 一.准备工作 需要用到的工具: 1.一台Window ...

  9. 使用 kubeadm 创建高可用 Kubernetes 及外部 etcd 集群

    博客链接:使用 kubeadm 创建高可用 Kubernetes 及外部 etcd 集群 前言 Kubernetes 的官方中文文档内容全面,表达清晰,有大量示例和解析 无论任何情况下都推荐先花几个小 ...

  10. Ai 文本生成式大模型 基础知识

    提示工程-RAG-微调 工程当中也是这个次序 提示词工程 RAG 微调 先问好问题 再补充知识 最后微调模型 RAG相关技术细节 选择合适的 Chunk 大小对 RAG 流程至关重要. Chunk 过 ...