P5290 [十二省联考2019]春节十二响
题目地址:P5290 [十二省联考2019]春节十二响
骗分方法
如果你实在一点思路也没有,暴力都不会打,那么请考虑一下骗分。
方法一
输出所有 \(M\) 的和。
期望得分:0分。
实际还有5分
方法二
注意到有 \(15\) 分为一条链,分两种情况考虑:
- 1号点有一个儿子——详见方法一。
- 1号点有两个儿子——把对这两个儿子下的两条链弄成两个堆,每次取出两个堆的堆顶,取 \(max\) 加入答案,当一个堆取尽后,把另一个堆里的所有元素加入答案,最后加入 \(M_1\) 。
期望得分:15分。
暴力方法
如果你的暴力时间复杂度很低并且常数很优秀,那么拿到一道题的大部分分数是很容易的。
方法三
可以写一个很高超的纯暴搜过掉数据较小的2~4个点。
然而说实话这道题写纯暴搜的难道貌似大于写正解2333
时间复杂度:不详。
期望得分:10~20分。
方法四
如果两个点是祖先后代关系,则在这两点之间连边,最终会形成一个 \(n\) 个点的图。则答案是这个图的一个最大独立集。
图的最大独立集是 NPC 问题,最快的方法貌似是状压, \(O(3^n)\) 。
期望得分:45分。
方法五
借用方法四的思想,如果 \(x,y\) 是祖先后代关系,则 \(a_{x,y}\) 为 \(1\) ,否则为 \(0\) ,这样可以构造出来一个 \(n \times n\) 的 01 矩阵。
按 \(M\) 值从大到小贪心地选。每选择一个就再从大到小把能选的都选上,然后把选择的这个的 \(M\) 值加入答案。
由于每次选完之后都要更新可选的集合,这个更新是 \(O(n)\) 的,而每次选择一个之后,还有要从大到小把能选的都选上,这个过程也是 \(O(n)\) 的,一共要进行 \(O(n)\) 次选择,所以总时间复杂度为 \(O(n^3)\) 的。
期望得分:45分。
方法六
从方法四的 \(O(3^n)\) 到方法五 \(O(n^3)\) ,期望得分没变海星。
考虑优化方法五,我们在方法五中看到这两个词:01 矩阵,集合。尝试用 bitset 优化,时间复杂度严格来讲没变,但是常数变成原来的 \(\frac{1}{64}\) 。
期望得分:60分。
方法七
换一种思路。
考虑类似方法二的第 \(2\) 种情况合并两颗子树。
时间复杂度: \(O(n^2)\) 。
期望得分:60分。
考场代码
我在考场上写出来的代码是方法六和方法二的结合版,得分为 \(75\) 分。
#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;
}
正确方法
方法八
方法七是 \(O(n^2)\) 的,思考瓶颈在哪儿?
合并两颗子树 \(x,y\) 时,我们相当于进行了 \(O(max(size_x,size_y))\) 。
能否将复杂度降到 \(O(min(size_x,size_y))\) ?
先考虑降复杂度之后,总的时间复杂度是多少?
降复杂度之后,相当于每一次合并,用 \(O(min(size_x,size_y))\) 扔掉了 \(min(size_x,size_y)\) 个元素。换句话说,扔掉一个元素是 \(O(1)\) 的。我们要把 \(n\) 个元素经过若干次合并,扔掉 \(O(n)\) 个元素,最终剩下 \(1\) 个元素。那么扔元素的复杂度为 \(O(n)\) ,考虑堆的影响,总时间复杂度为 \(O(n\ log\ n)\) 。
时间复杂度满足限制,可以放心的回去考虑如何降复杂度了。
当我们取完 \(min(size_x,size_y)\) 个元素后,一个堆是空的,另一个堆还剩下一些元素。
那我们直接把刚才取出来的元素再塞到非空的那个堆中不就完了么?
蛤?正解这么暴力?
我再告诉你,这个正解还有个好听的名字:启发式合并。
代码实现细节
有一个小细节是,代码实现中会出现 swap 两个堆的情况。
在 ouuan 的博客十二省联考2019 游记 & 题解中,对此有这样的说法:
swap 在不开 C++11 的情况下是 \(O(n)\) 的,开 C++11 则是 \(O(1)\) 的,如果不开 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;
}
P5290 [十二省联考2019]春节十二响的更多相关文章
- P5290 [十二省联考2019]春节十二响(堆+启发式合并)
P5290 [十二省联考2019]春节十二响 从特殊到一般 我们先看链的情况. 我们把点$1$左右的两条子链分别扔入堆里 每次取出两个堆的最大值,把答案累加上更大的那个(另一堆为空则直接加上去). 那 ...
- Luogu P5290 / LOJ3052 【[十二省联考2019]春节十二响】
联考Day2T2...多亏有这题...让我水了85精准翻盘进了A队... 题目大意: 挺简单的就不说了吧...(这怎么简述啊) 题目思路: 看到题的时候想了半天,不知道怎么搞.把样例画到演草纸上之后又 ...
- 【堆的启发式合并】【P5290】[十二省联考2019]春节十二响
Description 给定一棵 \(n\) 个节点的树,点有点权,将树的节点划分成多个集合,满足集合的并集是树的点集,最小化每个集合最大点权之和. Limitation \(1~\leq~n~\le ...
- Luogu P5290 [十二省联考2019]春节十二响
这题是最近看到的今年省选题中最良心的一道了吧 看题+想题+写题都可以在0.5h内解决,送分含义明显啊 首先理解了题意后我们很快就能发现两个点如果要被分在一段那么必须在它们的祖先处合并 首先我们考虑下二 ...
- luogu P5290 [十二省联考2019]春节十二响 优先队列_启发式合并
思维难度不大,在考上上写的启发式合并写错了,只拿了 60 pts,好难过QAQ 没什么太难的,在考场上想出链的部分分之后很容易就能想到正解.没错,就是非常短的启发式合并.注意一下,写的要漂亮一点,否则 ...
- 【题解】Luogu P5290 [十二省联考2019]春节十二响
原题传送门 每个点维护一个堆,表示这个点及其子树所需的每段内存的空间 搜索时从下向上做启发式合并堆中信息,最后根节点堆中所有内存空间之和就是答案 #include <bits/stdc++.h& ...
- [LOJ3052] [十二省联考 2019] 春节十二响
题目链接 LOJ:https://loj.ac/problem/3052 洛谷:https://www.luogu.org/problemnew/show/P5290 BZOJ:https://www ...
- Luogu5290 十二省联考2019春节十二响(贪心+启发式合并)
考虑链的做法,显然将两部分各自从大到小排序后逐位取max即可,最后将根计入.猜想树上做法相同,即按上述方式逐个合并子树,最后加入根.用multiset启发式合并即可维护.因为每次合并后较小集合会消失, ...
- LuoguP5290 [十二省联考2019]春节十二响 | 启发式合并
还有33天就要高考了,我在干啥-- 题目概述 一棵有根树,每个节点有权值. 要求把所有节点分成组,具有祖先-后代关系的两个节点不能被分到同一组. 每一组的代价是所包含的节点的最大权值,最小化所有组的代 ...
随机推荐
- [SCOI2016]萌萌哒
Luogu P3295 mrclr两周前做的题让蒟蒻的我现在做? 第一眼组合计数,如果把数字相同的数位看作一个整体,除了第一位不能为零,剩下的每一位都有$0$~$9$十种. 设不同的位数为$x$,那么 ...
- VUE中 style scoped 修改原有样式
作用域CSS 当<style>标记具有该scoped属性时,其CSS将仅应用于当前组件的元素.这类似于Shadow DOM中的样式封装.它有一些警告,但不需要任何polyfill.通过使用 ...
- Google第三方网站登录(JavaScript SDK)
官网:https://developers.google.com/identity/sign-in/web/ 一.创建应用 a.去谷歌控制台创建应用 网址:https://accounts.g ...
- elementUi源码解析(1)--项目结构篇
因为在忙其他事情好久没有更新iview的源码,也是因为后面的一些组件有点复杂在考虑用什么方式把复杂的功能逻辑简单的展示出来,还没想到方法,突然想到element的组件基本也差不多,内部功能的逻辑也差不 ...
- Oracle通过Navicat导入表数据与机构,数据无法直接查询,需要加双引号的问题
使用navicat 导入表到ORACLE时,总是会遇到虽然表格完整导入到数据库,但是往往查不出来数据,网上提供的解决办法是把查询的列 加上 双引号,或者表名加上双引号,但这解决办法却减慢了编写sql ...
- vue应用或者是项目其实就是 实例(完成基本逻辑) + 组件(单文件组件,全局组件,局部组件,内置组件)来完成 ;
以上! 组件里面包含HTML,css, js,也就是一个完整的功能!
- Django rest framework 使用haystack对接Elasticsearch
Elasticsearch 介绍 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是 ...
- php之swoole安装与基本使用
扩展安装: 参考GitHub地址 安装: 1. 使用PHP官方的PECL工具安装 (初学者) pecl install swoole 2. 从源码编译安装 (推荐) git clone https:/ ...
- JAVA ==号和equals()的区别
==号和equals()方法都是比较是否相等的方法,那它们有什么区别和联系呢? 首先,==号在比较基本数据类型时比较的是值,而用==号比较两个对象时比较的是两个对象的地址值: int x = 10; ...
- STS启动springboot项目,加载不了resources下的配置文件的问题
从这篇博客的评论中找到了解决方案 答案: eclipse的设置中,它默认是不包括resources下的文件的,把它改了就行了 原本用idea没这些事的,不过idea旗舰版到期了,社区版的话,对前端又没 ...