Codeforces Round #471 (Div. 2) F. Heaps(dp)

题意

给定一棵以 1 号点为根的树。若满足以下条件,则认为节点 p 处有一个 k 叉高度为 m 的堆:

  • m=1 ,则 p 本身就是一个 k 叉高度为 1 的堆。
  • m>1 ,则 p 需要有至少 k 个儿子满足在儿子处有一个 k 叉高度为 m1 的堆。

dp[p][k] 表示在 pk 叉堆的最大高度,令 g[p][k]p 子树内最大的 dp[v][k]i=1nj=1ng[i][j]

n3105

题解

如果固定一个 k ,然后直接暴力做 dp ,每次是 O(n2) 的。

但显然是没必要这么暴力的,因为我们发现对于任意 k>1 ,都存在 dp[p][k]logkn

所以我们可以考虑做对于这个 dp 值的 dp ,具体来说令 f[p][x] 为满足 dp[p][k]x 的最大的 k

不难发现这样总状态是 O(nlogn) 的,接下来我们只需要考虑如何转移这个 dp 了。

考虑枚举一个点 p ,然后枚举它当前的层数 x20 ,然后考虑从它所有儿子的 x1 的状态转移过来。

这个点的 f 取值显然不会超过儿子总数,然后考虑从大到小枚举 f 的取值 k 然后判断 f[v][x1]vu 的一个儿子)中第 k 大的取值是否 k ,如果可以则能取这个值。

这是因为它有 k 个儿子都满足至少具有 k 叉树的条件,那么这个点也能满足 k 叉树的条件。

然后最后记得要考虑 k=1 的情况(直接找向下最长链),然后答案就是 i(depi+j(f[i][j]1))

复杂度就是 O(nlogn)×O(logn)=O(nlog2n) 的(因为有排序)。

总结

如果对于 dp 状态很多,但是取值很小的题,可以考虑对于 dp 值进行转移,常常状态就可以压到很少。

代码

其实很好写qwq

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define pb push_back using namespace std; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("F.in", "r", stdin); freopen ("F.out", "w", stdout); #endif } const int N = 3e5 + 1e3, Lim = 20; vector<int> G[N]; int n, f[N][Lim + 1], g[N], len; void Dp(int u, int fa) { for (int v : G[u]) if (v != fa) Dp(v, u); f[u][1] = n; For (i, 2, Lim) { len = 0; for (int v : G[u]) if (v != fa) g[++ len] = f[v][i - 1]; sort(g + 1, g + len + 1, greater<int>()); Fordown (k, len, 1) if (g[k] >= k) { f[u][i] = k; break; } } } int dep[N]; void Dfs(int u, int fa) { dep[u] = 1; for (int v : G[u]) if (v != fa) { Dfs(v, u); chkmax(dep[u], dep[v] + 1); For (i, 1, Lim) chkmax(f[u][i], f[v][i]); } } long long ans = 0; int main () { File(); n = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].pb(v); G[v].pb(u); } Dp(1, 0); Dfs(1, 0); For (i, 1, n) { ans += dep[i]; For (j, 1, Lim) if (f[i][j]) ans += f[i][j] - 1; } printf ("%lld\n", ans); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9772464.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(164)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示