当前位置: 首页 > news >正文

洛谷 P9847 [ICPC 2021 Nanjing R] Crystalfly

安利一发洛谷博客

原题传送门

你说得对,但是 pjsk 更好玩捏(ena 误入)。

题意简述

每个点上有一些晶蝶,你从根节点(111 号节点)开始走,当你到达这个节点的父亲(总不可能先到它的儿子吧……)时,过一段时间晶蝶就会飞走,求你能抓到的晶蝶最大值。

走法

首先肯定不会停住不动吧……

所以最优策略肯定是不断走。

而且时间限制其实是没用的,因为到后面晶蝶都没了你在抓什么……

现在我们考虑走到了 uuu 节点,它的儿子分别是 v1,v2,⋯,vkv_1,v_2,\cdots,v_kv1,v2,,vk。同时我们不考虑以 uuu 的子树以外的晶蝶。

其实就是把 uuu 当作根节点进行观察……

那么 uuu 一共有这么几种走法(注意,往后的“走一步”均代表在一秒的时间内移动):

零:向上走,再走回来

这样肯定是不优的,因为你最后还是会回来,但是一些晶蝶已经飞走了,所以这样走的两步肯定是不划算的,这种情况就不考虑了(所以是第零种)。

一:走进一个儿子 viv_ivi,继续往下走

这样的话 uuu 的其他儿子肯定是吃不到(就是抓不到晶蝶了,原谅我这个习惯)了。

所以此时就应该把 viv_ivi 的子树吃完再回退。

二:走到一个儿子 viv_ivi,再退回来

接着我们就应该走到另外一个儿子了,不然再走到 viv_ivi 不划算(参考第零种走法)。

那么应该走到哪些孩子呢?

注意到走到另一个孩子,时间已经过去了 333 秒,所以应该走向 tvj=3t_{v_j}=3tvj=3 的儿子 vjv_jvj(不懂的可以评论问我)。

之后吃掉 vjv_jvj 的子树就好啦!

毕竟再直接回退就没意思了,其他儿子的晶蝶早没了。

如何动态规划?

肯定有人会说:算法标签不都写着嘛,你还有什么好说的。

这种人请跳过这篇题解。

毕竟你上考场又没有标签,所以还是得自己分析的。


众所周知,能够动态规划需要 222 个条件。

最优子结构

明显,如果每个子树我们都抓到了最多的晶蝶,那么总体来看绝对就是最优解。因为一个节点只会影响直接儿子和兄弟节点,兄弟节点的儿子不会受到影响。

无后效性

每个子树内部怎么抓不影响其他子树,所以我们不需要关心怎么抓的晶蝶。


那么我们已经明确了可以用动态规划,现在就来思考怎么实现吧!

动态规划的实现

我们用三个数组记录状态(你用二维数组我也不拦你):

fif_ifi 记录 iii 的子树(不包括 iii)最多能抓多少晶蝶;

gig_igi 记录 iii 的子树(包括 iii)最多能抓多少晶蝶;

hih_ihi 记录 iii 的子树(包括 iii)在直接儿子的晶蝶全部飞走的情况下最多能抓到多少晶蝶。

gig_igi

明显,gig_igi 就比 fif_ifi 多了一个根节点(指子树范围内)。

所以:
gi=fi+aig_i=f_i+a_i gi=fi+ai

hih_ihi

由于儿子的晶蝶都没了,你只要不走下去就不会惊扰到子树里其余的晶蝶。

所以我们把每个儿子的 fff 加起来就好啦!

我们假设 iii 的儿子分别是 v1,v2,⋯,vmv_1,v_2,\cdots,v_mv1,v2,,vm,就有:
hi=∑k=1mfvk+aih_i=\sum_{k=1}^{m}f_{v_k} + a_i hi=k=1mfvk+ai

fif_ifi

重点来了!!!

首先如果用第一种走法,只有某个儿子 vjv_jvj 是能被吃到的,因此
fi′=max⁡j∈[1,k](gvj−fvj+∑k=1mfvk)f_i'=\max_{j\in[1,k]}(g_{v_j} - f_{v_j} + \sum_{k=1}^{m}f_{v_k}) fi=j[1,k]max(gvjfvj+k=1mfvk)

而如果运用第二种走法,第一个走到的儿子 vjv_jvj 的直接儿子就吃不到了(自己思考一下),也就是 hhh,而第二个走到的儿子 vuv_uvu 可以吃到 ggg,其余只能吃到 fff

vjv_jvj 怎么选呢?

明显对于任意一个儿子(当然要 t=3t=3t=3),你选了它和没选它,差的只是它自己,再往下的最优还是最优。

所以我们要找的就是 t=3t=3t=3aaa 最大的儿子。

于是:
fi′′=max⁡j∈[1,k](hvj−fvj+∑k=1mfvk+gvu−fvu)f''_i=\max_{j\in[1,k]}(h_{v_j} - f_{v_j} + \sum_{k=1}^{m}f_{v_k} + g_{v_u} - f_{v_u}) fi′′=j[1,k]max(hvjfvj+k=1mfvk+gvufvu)
其中 vuv_uvu 就是我们选到的第二个儿子。

综上,就有
fi=max⁡(fi′,fi′′)f_i=\max(f'_i,f''_i) fi=max(fi,fi′′)

一些小问题(优化)

求和

明显,如果直接求 ∑k=1mfvk\sum_{k=1}^{m}f_{v_k}k=1mfvk,每个点都会作为儿子被访问一遍(不要纠结根节点啦),总共就是 O(n2)O(n^2)O(n2),不行。

那就预处理呗。搜到某个节点就先处理出来不久好了?

我们用 sss 记录这个和,即
s=∑k=1mfvks=\sum_{k=1}^{m}f_{v_k} s=k=1mfvk
那么就有
gi=fi+aihi=s+aifi′=max⁡j∈[1,k](gvj−fvj+s)fi′′=max⁡j∈[1,k](hvj−fvj+s+gvu−fvu)fi=max⁡(fi′,fi′′)g_i=f_i+a_i\\ h_i=s + a_i\\ f_i'=\max_{j\in[1,k]}(g_{v_j} - f_{v_j} + s)\\ f''_i=\max_{j\in[1,k]}(h_{v_j} - f_{v_j} + s + g_{v_u} - f_{v_u})\\ f_i=\max(f'_i,f''_i) gi=fi+aihi=s+aifi=j[1,k]max(gvjfvj+s)fi′′=j[1,k]max(hvjfvj+s+gvufvu)fi=max(fi,fi′′)
是不是简便很多?

vuv_uvu 怎么求?

最朴素的做法是每次找一遍最大值,时间复杂度 O(n2)O(n^2)O(n2),完蛋……

于是我们需要预处理最大值。

但是可能会有冲突(vj=vuv_j=v_uvj=vu)怎么办?那就同时记最大值和次大值(可以相等)就好啦!当然同时记得要记录位置,用 pair<int, int> 就好了。

所以这个也顺利解决了。

gig_igi

没错,gig_igi 其实是辅助理解用的,完全可以直接替换成 fi+aif_i+a_ifi+ai

状态转移方程

hi=s+aifi′=max⁡j∈[1,k](s+avj)fi′′=max⁡j∈[1,k](hvj−fvj+s+avj)fi=max⁡(fi′,fi′′)h_i=s + a_i\\ f_i'=\max_{j\in[1,k]}(s + a_{v_j})\\ f''_i=\max_{j\in[1,k]}(h_{v_j} - f_{v_j} + s + a_{v_j})\\ f_i=\max(f'_i,f''_i) hi=s+aifi=j[1,k]max(s+avj)fi′′=j[1,k]max(hvjfvj+s+avj)fi=max(fi,fi′′)

AC 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 说白了就是不喜欢 #define int long longconst int N = 101000; // 数组不够大,亲人两行泪 qwq
const ll inf = 1ll << 60;vector<int> e[N];int n, a[N], t[N];ll f[N], h[N];void dfs(int u, int par) {ll s = 0;int ma = 0;for (auto v : e[u]) if (v != par) {dfs(v, u);s += f[v]; // 统计和ma = max(ma, a[v]); // f'}f[u] = s + ma;pair<ll, int> ma1(-inf, 0), ma2(-inf, 0); // 最大值和次大值,第一位是数,第二位是位置for (auto v : e[u]) if (v != par && t[v] == 3) {pair<ll, int> ma3(a[v], v);if (ma2 < ma3) ma2 = ma3;if (ma1 < ma2) swap(ma1, ma2); // 更新}for (auto v : e[u]) if (v != par) {ll tmp = s + h[v] - f[v];if (v == ma1.second) tmp += ma2.first;else tmp += ma1.first;f[u] = max(f[u], tmp); // f''}h[u] = s + a[u]; // h
}void solve() {scanf("%d", &n);for (int i = 1; i <= n; i++)e[i].clear();for (int i = 1; i <= n; i++)scanf("%d", &a[i]);for (int i = 1; i <= n; i++)scanf("%d", &t[i]);for (int i = 2; i <= n; i++) {int u, v;scanf("%d%d", &u, &v);e[u].push_back(v);e[v].push_back(u);}dfs(1, 0);printf("%lld\n", f[1] + a[1]);
}int main() {int T;scanf("%d", &T);for (; T; T--) {solve();}
}
http://www.dtcms.com/a/585091.html

相关文章:

  • X光机AI系统实现轮胎缺陷识别准确率超97%
  • Depth Anything with Any Prior解读
  • Vue2 学习记录--语法部分
  • bluetoothctl命令
  • 泰安做网站多少钱什么网站做ppt
  • 备案 网站负责人 法人今天重大新闻头条新闻军事
  • Android16 EDLA HDMI OUT投屏默认通过设置
  • flink1.20.2环境部署和实验-2
  • TCP滑动窗口:网络世界的“智能流量阀门”
  • TCP全连接队列与tcpdump抓包
  • 感知机:乳腺癌分类实现 K 均值聚类:从零实现
  • 【Linux】Linux 地址空间 + 页表映射的概念解析
  • 【Linux篇】System V IPC详解:共享内存、消息队列与信号量
  • GLM4.6多工具协同开发实践:AI构建智能任务管理系统的完整指南
  • LangChain v1.0 快速入门
  • 云南网站建设找天软东莞网站建设什么价格便宜
  • AI Agent设计模式 Day 4:ReWOO模式:推理而不观察的高效模式
  • 38.华为云存储类服务核心配置
  • 使用 SQLAlchemy 操作单表:以 SQLite 用户表为例的完整实战指南
  • 新余教育网站建设企业网站赏析
  • Flink CDC 从 Definition 到可落地 YAML
  • 深入理解C语言字符串复制:从基础实现到优雅设计
  • SQL注入之堆叠及waf绕过注入(安全狗)
  • 微信小程序开发案例 | 极简清单小程序(下)
  • 37.华为云网络类云服务
  • Java设计模式精讲---04原型模式
  • 有哪些网站是可以做免费推广的做视频网站要多大的服务器
  • 线代强化NO1|行列式及矩阵
  • Shelly智能模块:家居科技革新之选
  • 网页Iframe读取PDF文件的参数设置