题解:P11492 [BalticOI 2023] Minequake

· · 题解

没有题解?没有评级?我写一篇。

我们在手玩样例的时候会发现每次从儿子到父亲就像是把整个儿子的子树中所有的值加上一个前置的时间,最后合并道子树上边。

合并子树信息类型的题目,考虑上树形 dp。

但是我们合并只能往根,加上一个换根 dp 就好,看下数据范围是 O(n) 或者 O(n\log n),姑且认为这个思路值得一试。

先设 f[u] 表示 u 的子树中从 u 出发,到各个点第一次时间的最小值,这个时候 f[root] 就是我们从根节点出发的答案了,换根 dp 的标准套路。

我们不难想出来这么一个转移方程:f[u]=\sum_{v\in son[u]} f[v] + siz[v] \times (2 \times siz[u] - 1)

这个 siz[u] 就是正常的子树大小,和 f[u] 同时更新,也就是 siz[u] 在计算完 dp[v] 后回立刻更新。

并不难理解,相当于累加了前置所需的时间。

这个时候有的人就要问了,BaiBaiShaFeng 你这个东西的转移明明是依靠顺序的啊,凭什么这个就是对的。

其实这个东西不依靠任何的顺序,如果你用 v_i 表示第 i 个儿子,之后将 siz[u] 拆成 i 之前的儿子的子树大小和,再进行拆式子,你会发现这个东西确确实实不论顺序,最后的总和是一样的。

然后我们考虑怎么换根。

我们设 dp[u] 表示以 u 为根的答案,v 是它的子节点。

然后就没有了。 代码如下。 ```cpp #include <bits/stdc++.h> #define int long long using namespace std; const int MN=1e6+116; struct Node{ int nxt, to; }node[MN]; int head[MN], tottt; void insert(int u, int v){ node[++tottt].to=v; node[tottt].nxt=head[u]; head[u]=tottt; return; } int n, f[MN], dp[MN], siz[MN], ans; void dfs1(int u, int father){ siz[u]=1; for(int i=head[u];i;i=node[i].nxt){ int v=node[i].to; if(v==father) continue; dfs1(v,u); f[u]+=f[v]+siz[v]*(2*siz[u]-1); siz[u]+=siz[v]; } } void dfs2(int u, int father){ for(int i=head[u];i;i=node[i].nxt){ int v=node[i].to; if(v==father) continue; dp[v]=dp[u]-n+2*siz[v]; ans=min(dp[v],ans); dfs2(v,u); } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n; for(int i=1; i<n; ++i){ int u, v; cin>>u>>v; insert(u,v); insert(v,u); } dfs1(1,0); ans=dp[1]=f[1]; dfs2(1,0); cout<<ans<<'\n'; return 0; } ```