1 条题解
-
0
自动搬运
来自洛谷,原作者为

xht
好想爱这个世界啊搬运于
2025-08-24 22:09:06,当前版本为作者最后更新于2019-04-11 04:50:11,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目地址:P5290 [十二省联考2019]春节十二响
骗分方法
如果你实在一点思路也没有,暴力都不会打,那么请考虑一下骗分。
方法一
输出所有 的和。
期望得分:0分。
实际还有5分方法二
注意到有 分为一条链,分两种情况考虑:
- 1号点有一个儿子——详见方法一。
- 1号点有两个儿子——把对这两个儿子下的两条链弄成两个堆,每次取出两个堆的堆顶,取 加入答案,当一个堆取尽后,把另一个堆里的所有元素加入答案,最后加入 。
期望得分:15分。
暴力方法
如果你的暴力时间复杂度很低并且常数很优秀,那么拿到一道题的大部分分数是很容易的。
方法三
可以写一个很高超的纯暴搜过掉数据较小的2~4个点。
然而说实话这道题写纯暴搜的难道貌似大于写正解2333时间复杂度:不详。
期望得分:10~20分。
方法四
如果两个点是祖先后代关系,则在这两点之间连边,最终会形成一个 个点的图。则答案是这个图的一个最大独立集。
图的最大独立集是 NPC 问题,最快的方法貌似是状压, 。
期望得分:45分。
方法五
借用方法四的思想,如果 是祖先后代关系,则 为 ,否则为 ,这样可以构造出来一个 的 01 矩阵。
按 值从大到小贪心地选。每选择一个就再从大到小把能选的都选上,然后把选择的这个的 值加入答案。
由于每次选完之后都要更新可选的集合,这个更新是 的,而每次选择一个之后,还有要从大到小把能选的都选上,这个过程也是 的,一共要进行 次选择,所以总时间复杂度为 的。
期望得分:45分。
方法六
从方法四的 到方法五 ,期望得分没变海星。
考虑优化方法五,我们在方法五中看到这两个词:01 矩阵,集合。尝试用 bitset 优化,时间复杂度严格来讲没变,但是常数变成原来的 。
期望得分:60分。
方法七
换一种思路。
考虑类似方法二的第 种情况合并两颗子树。
时间复杂度: 。
期望得分:60分。
考场代码
我在考场上写出来的代码是方法六和方法二的结合版,得分为 分。
#include <bits/stdc++.h> #define ll long long #define pii pair<int, int> #define mp make_pair using namespace std; const int N = 2e5 + 6; int n, a[N], f[N]; ll ans = 0; inline int rd() { int x = 0; char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') x = x * 10 + (ch - '0'), ch = getchar(); return x; } inline bool pd1() { for (int i = 2; i <= n; i++) if (f[i] != i - 1) return 0; return 1; } inline void P101112_1() { for (int i = 1; i <= n; i++) ans += a[i]; cout << ans << endl; } namespace P101112_2 { int deg[N]; inline bool pd() { for (int i = 2; i <= n; i++) ++deg[f[i]]; if (deg[1] != 2) return 0; for (int i = 2; i <= n; i++) if (deg[i] > 1) return 0; return 1; } int son[N]; priority_queue<int> q[2]; inline void work() { int g[2], t = 0; for (int i = 2; i <= n; i++) if (f[i] == 1) g[t++] = i; else son[f[i]] = i; ans = a[1]; for (int i = 0; i < 2; i++) { int x = g[i]; while (x) q[i].push(a[x]), x = son[x]; } while (q[0].size() && q[1].size()) ans += max(q[0].top(), q[1].top()), q[0].pop(), q[1].pop(); for (int i = 0; i < 2; i++) if (q[i].size()) while (q[i].size()) ans += q[i].top(), q[i].pop(); cout << ans << endl; } } namespace TX { const int M = 2e3 + 6; bitset<M> b[M], o, v; vector<int> e[M]; int st[M], top = 0, p[M]; pii g[M]; void dfs(int x) { b[p[x]] = o; for (int i = 1; i <= top; i++) b[p[st[i]]][p[x]] = 0; st[++top] = x; o[p[x]] = 0; for (unsigned int i = 0; i < e[x].size(); i++) { int y = e[x][i]; if (!o[p[y]]) continue; dfs(y); } o[p[x]] = 1; --top; } inline void work() { for (int i = 2; i <= n; i++) e[f[i]].push_back(i); for (int i = 1; i <= n; i++) g[i] = mp(a[i], i); sort(g + 1, g + n + 1); for (int i = 1; i <= n; i++) p[g[i].second] = i; o.set(); dfs(1); v.reset(); for (int i = n; i; i--) { if (v[i]) continue; o = b[i]; ans += g[i].first; v[i] = 1; for (int j = i - 1; j; j--) if (o[j] && !v[j]) { v[j] = 1; o &= b[j]; } } cout << ans << endl; } } int main() { cin >> n; for (int i = 1; i <= n; i++) a[i] = rd(); for (int i = 2; i <= n; i++) f[i] = rd(); if (pd1()) { P101112_1(); return 0; } if (P101112_2::pd()) { P101112_2::work(); return 0; } if (n < 2001) { TX::work(); return 0; } return 0; }正确方法
方法八
方法七是 的,思考瓶颈在哪儿?
合并两颗子树 时,我们相当于进行了 。
能否将复杂度降到 ?
先考虑降复杂度之后,总的时间复杂度是多少?
降复杂度之后,相当于每一次合并,用 扔掉了 个元素。换句话说,扔掉一个元素是 的。我们要把 个元素经过若干次合并,扔掉 个元素,最终剩下 个元素。那么扔元素的复杂度为 ,考虑堆的影响,总时间复杂度为 。
时间复杂度满足限制,可以放心的回去考虑如何降复杂度了。
当我们取完 个元素后,一个堆是空的,另一个堆还剩下一些元素。
那我们直接把刚才取出来的元素再塞到非空的那个堆中不就完了么?
蛤?正解这么暴力?我再告诉你,这个正解还有个好听的名字:启发式合并。
代码实现细节
有一个小细节是,代码实现中会出现 swap 两个堆的情况。
在 ouuan 的博客十二省联考2019 游记 & 题解中,对此有这样的说法:
swap 在不开 C++11 的情况下是 的,开 C++11 则是 的,如果不开 C++11 可以记录 id 然后交换 id 。
最终的代码真心好写而且好短QwQ#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 6; int n, a[N], f; vector<int> e[N], o; priority_queue<int> q[N]; inline void merge(int x, int y) { if (q[x].size() < q[y].size()) swap(q[x], q[y]); while (q[y].size()) { o.push_back(max(q[x].top(), q[y].top())); q[x].pop(), q[y].pop(); } while (o.size()) q[x].push(o.back()), o.pop_back(); } void dfs(int x) { for (unsigned int i = 0; i < e[x].size(); i++) dfs(e[x][i]), merge(x, e[x][i]); q[x].push(a[x]); } int main() { cin >> n; for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 2; i <= n; i++) scanf("%d", &f), e[f].push_back(i); dfs(1); long long ans = 0; while (q[1].size()) ans += q[1].top(), q[1].pop(); cout << ans << endl; return 0; }
- 1
信息
- ID
- 4274
- 时间
- 1000ms
- 内存
- 500MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者