首页 > 其他分享 >[NOI]2024 登山 题解

[NOI]2024 登山 题解

时间:2024-08-19 22:16:36浏览次数:6  
标签:那么 限制 NOI 题解 类点 2024 lim 节点 DP

好像在洛谷题解区里还没人和我做法一样,,?

考场做法,只用到了倍增和线段树,感觉挺好写。考场上从开题到过题只用了 2h。

最底下有省流版(?)。

以下是我考场里比较详细的思路,所以比较长。

先考虑如何 \(O(n^2)\) 做,然后再想优化。

容易先想到一个状态数是 \(O(n^2)\) 的 DP,即记录起点,并将向上跳多少的限制记在状态里。这个 DP 的转移顺序看起来比较的混乱。那么我们再观察一下这个转移的过程。

记一个节点 \(x\) 的限制为至少跳到深度为 \(v_x\) 的祖先,\(v_x=d_x-(h_x+1)\)。我们改 \(l_x\) 与 \(r_x\) 的定义为能跳到的祖先深度区间。

假如说我现在以节点 \(x\) 作为起点,首先有个限制 \(lim=v_x\),然后接下来走一步会有两种选择:

  1. 向上跳到祖先 \(p\),那么限制会变为 \(lim=\min(lim,v_p)\),容易发现一定会是 \(lim=v_p\)。
  2. 向下走到一个儿子 \(y\),那么限制会变为 \(lim=\min(lim,v_y)\)。

在第一种情况里,限制只跟 \(v_p\) 有关。因此可以看作以节点 \(p\) 作为起点。那么此时就变成了一个相同的子问题。

在第二种情况里,若 \(lim\) 不变,那么限制还是 \(v_x\);若 \(lim\) 发生了改变,那么限制就只跟 \(v_y\) 有关了。假如说 \(lim\) 发生了改变,那么就又可以将 \(y\) 作为一个新的起点。

那么此时我们就有了一个状态数 \(O(n)\) 的 DP:\(f(x)\) 表示以 \(x\) 作为起点的方案数。

由上述分析我们容易知道转移的顺序:因为 \(lim\) 是越来越小的,所以我们按 \(v_x\) 从小到大求出每个 \(f(x)\) 即可。

那么我们也容易得知求 \(f(x)\) 的一个暴力转移:

从 \(x\) 开始向子树内 BFS,会遇到两种点。

  1. 如果碰到了已经求过 DP 值的节点,那么这个节点的限制一定比 \(x\) 严格,直接用这个节点的 DP 值更新 \(f(x)\)。
  2. 若 BFS 到的当前节点还没有求出 DP 值,那么计算从这个节点向上跳的方案数(即将其可以向上跳到的节点与 \(x\) 的限制求交得到的所有节点的 DP 值之和),然后继续向下 BFS。

这个 DP 暴力转移是 \(O(n^2)\) 的,瓶颈在于上述 BFS 的过程。

对于 1 类点来说,我们希望找到从 \(x\) 向下 BFS 第一次碰到的所有限制严于 \(x\) 的所有节点的 DP 值。

对于 2 类点来说,我们希望把 \(x\) 子树中比 \(x\) 限制更严的点的子树删掉,剩下的点就是 2. 中的情况。

此时我不知道从何处下手了,接着想到了一个不知道能不能继续做的做法:求了根号次 DP 值就重构?但是这个对我来说太难往下继续思考了。

于是我先看了部分分:树为一条链。

这种情况下,对于 \(x\) 来说,向它子树内 BFS 时碰到的 1 类点只有一个,2 类点形成了一段区间。我们记 \(nxt_x\) 代表 \(x\) 子树内的 1 类点(即 \([x+1,n]\) 中第一个限制严于 \(x\) 的点)。

那么 \(f(x)\) 就很好求了(注意 \(l_x,r_x\) 的含义与原题中的含义不同,在题解一开始的时候讲过):

\[f(x)=f(nxt_x)+\sum_{x<y<nxt_x}\sum_{l_y\leq i\leq \min(r_y,v_x)}f(i) \]

其中 \(\sum f(i)\) 可以用前缀和表示,记 \(s(x)\) 为 \(f\) 的前缀和,那么

\[f(x)=f(nxt_x)+\sum_{x<y<nxt_x}s(\min(r_y,v_x))-s(l_y-1) \]

其中 \(-s(l_y-1)\) 与 \(x\) 无关,可以直接预处理,\(s(\min(r_y,v_x))\) 也可以根据 \(r_y\) 与 \(v_x\) 的大小关系来分类讨论,从而去掉 \(\min\)。

总结一下,我们得出了树为一条链的做法:从前往后扫描,碰到 \(l_y-1\) 的时候将 \(-s(l_y-1)\) 的贡献放在 \(y\) 上,碰到 \(r_y\) 的时候将 \(s(r_y)\) 的贡献放在 \(y\) 上。碰到 \(v_x\) 的时候求 \(f(x)\) 的值,我们需要求出 \((x,nxt_x)\) 中的贡献之和,以及有多少个 \(y\) 假了 \(-s(l_y-1)\) 的贡献但是没有加 \(s(r_y)\) 的贡献:这些 \(y\) 都对 \(x\) 产生了 \(s(v_x)\) 的贡献。

这个统计 2 类点对 \(x\) 造成贡献的方法可以很容易地拓展到树上。那么我们又回到了一个问题上:我们如何删掉一个子树?

此时我想到了一个做法:用 \(u(x)=0/1\) 代表 \(x\) 有没有被删除。求出一个点 \(x\) 的 DP 值时,将 \(x\) 子树里的点的 \(u\) 值全都改为 \(1\)。

这显然是个错的做法:我们 DFS 到 \(x\) 子树里的时候可是要用到这个子树里的点啊,怎么能删掉呢?可是我们 DFS 到 \(x\) 子树时,直接遍历 \(x\) 子树将 \(u\) 值全都变为 \(0\) 也是不对的,因为 \(x\) 子树里可能已经有要删掉的子树了。

那么我们可以更改 \(u(x)\) 的定义,将有没有被删除改成被删除了几次。这时候豁然开朗了:我惊奇地发现,\(x\) 子树里的 \(u\) 值,它们都 \(\ge u(x)\),并且 \(x\) 子树里的 2 类点,它们的 \(u\) 值都等于 \(u(x)\)!

(这个东西证明很简单,但是我考场上没有思考正确性我就不写了)

那么我们利用 DFS 序将子树变成区间,记录一个 pair,表示区间中 \(u\) 的最小值,以及 \(u\) 等于最小值的那些点的信息就能求出所有 2 类点的贡献了!

1 类点的贡献也迎刃而解了:在打删除标记的时候不将子树的根打标记,然后维护 \(u\) 最小值的 DP 值之和即可。(2 类点的 DP 值都为 \(0\) 所以 2 类点不会造成影响)

省流

记 \(f(x)\) 为从 \(x\) 开始走的答案。假如走到某个点,限制突然改变了那么就可以直接用这个点的 \(f\) 值用来更新 \(f(x)\)。

向上跳肯定会改变限制,那么考虑朝子树里走的情况:

  1. 朝子树里走到一个改变限制的点,需要求出这些点的 \(f\) 之和。
  2. 走到一个不改变限制的点,然后向上跳。

第二种情况是跳到一条祖先后代链,假如说找到了这些点就可以用前缀和解决。

那么我们来找这些点。我们求出一个点的 \(f\) 就对其子树里的所有点打上一个删除标记。记 \(u(x)\) 表示 \(x\) 被删除了几次。那么求 \(f(x)\) 时,所有 \(u\) 值等于 \(u(x)\) 的那些点都是第二种情况里的点。通过 DFS 序将子树变成区间,用线段树维护 pair 的方法维护区间内 \(u\) 最小值的信息即可。

第一种情况就是打删除标记时不将根打标记即可。复杂度 \(O(n\log n)\)。

代码只拍了照片,而且写的比较丑陋,如果某天我想写了就重写一份吧(

代码照片

标签:那么,限制,NOI,题解,类点,2024,lim,节点,DP
From: https://www.cnblogs.com/tevenqwq/p/18368230

相关文章

  • AGC002 题解
    目录A-RangeProductB-BoxandBallC-KnotPuzzleA-RangeProduct分情况讨论:\(a\le0\leb\)时,乘积一定为\(0\);否则:\(0<a\leb\)时,乘积一定为正;否则,负数的个数有\(b-a+1\)个,判断这个数是否为奇数,若是,乘积为负,否则为正。#include<bits/stdc++.h......
  • ControlNeXt: Powerful and Efficient Control for Image and Video Generation(2024,
    ControlNeXt:PowerfulandEfficientControlforImageandVideoGeneration(2024,8)paperGithub进一步在ControlNet上进行了改进,主要针对一下两点对于每一个模块添加一个Zero-Conv也会占用很多显存.Zero-Conv两个模态的输出的mean、var具有差异,导致收敛很慢.针对1,......
  • 2024.8.4~2024.8.18济南北斗学友集训
    8.9晚上原神(原题之神)争霸赛(挑选写过的6题进行比赛)rk前7名可以许一个50r以内的愿望100+100+0+100+??+(30+)=330+18:05Begin18:??T1100pts18:??T2100pts18:54T4100pts19:42T5??ptsO(kn)worst(intree)......
  • 笔试题(2024/8/19)
    一、简答题1.简述#ifdef、#else、#endif和#iFndef的作用#ifdef、#else、#endif和#ifndef 是C/C++中的预处理指令,用于条件编译。它们的作用是根据条件来控制代码的编译过程。#ifdef(即“ifdefined”)指令用于检查一个宏是否已定义。如果该宏已被定义,则编译下面的代码......
  • P10423题解
    P10423[蓝桥杯2024省B]填空问题先贴上答案#include<iostream>usingnamespacestd;intmain(){stringans[]={"1204","1100325199.77",};charT;cin>>T;cout<<ans[T-'A']......
  • P10155题解
    1题意给定一个排列ppp,每次可以选择一个数pi......
  • AT_abc027_b题解
    说明需要掌握贪心算法。这么简单为什么是黄题啊?题意给定一个长度为的非负整数序列,你可以进行若干次操作,每次操作都可以选择一个长度为的子串,花费的代价,将其中的每个数都变成该子串的平均值,现在你必须将每个数都变成相同的,你必须同时保证每个数为非负整数。分析先算......
  • [Ynoi Easy Round 2021] TEST_152
    题目链接:[YnoiEasyRound2021]TEST_152一道思路接近却比这道题难点的题目[Ynoi2012]NOIP2015充满了希望经典结论:无论怎么覆盖,总段数都是\(O(覆盖次数)\)的。证明的话,考虑到每次推平只会使得左右端点的段分裂开,使得段数+1,而中间的段直接被覆盖,所以最多总段数只会为......
  • 2024.8 总结
    杂题【YBOJ】Pair题目描述给出二维平面上的\(n\)个点,第\(i\)个点的坐标为\(x_i,y_i\)。定义点\(i\)与点\(j\)之间的距离为\(\frac{|x_i-x_j|+|y_i-y_j|}{\sqrt{(x_i-x_j)^2+(y_i-y_j)^2}}\),求平面上两点的距离最大为多少。($1\len\le10^5$)解题思路首先,我们......
  • 2024.8.19
    #include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<string.h>#include<stdlib.h>intmain(){ //1.创建套接字 intsock_fd=socket(AF_I......