P5021 赛道修建 题解
简要题意:
在一棵树上求 \(m\) 条不相交的路径的最小值的最大值。
本题部分分很多,而且本人也交了 \(27\) 次,所以一定要仔细讲部分分!
算法一
对于 \(b_i = a_i + 1\) 的数据,你发现这是一条链。
也就是说,对这部分数据,题目简化为:
将一个数组分为不相交的若干区间,使得它们权值和的最小值最大。
一看,最小值最大,就是二分答案。
验证方法也很简单。
假设你当前的和是 \(\text{sum}\),当前决策的数为 \(x\),验证的和为 \(ans\).
此时如果 \(sum+x>ans\),那么将前面一段与当前的决策作为一段区间,并开启新的区间。
否则,不开启新的区间。
时间复杂度: \(O(n \log n)\).
实际得分:\(20pts\).
(但是,如果你对所有情况都按链做,似乎还可以都对一个点)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,a[N],f=0;
vector<pair<int,int> >G[N];
bool chain=0; //链的部分分
inline void dfs(int dep,int bs) {
// printf("%d %d\n",dep,bs);
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i].first,y=G[dep][i].second;
if(x==bs) continue;
dfs(x,dep); a[dep]=y;
}
} //把权值做成数组取出
inline bool check(int dep) {
int t=0,sum=0;
for(int i=1;i<n;i++) {
if(sum+a[i]>=dep) sum=0,t++; //新开一个区间
else sum+=a[i]; //区间继承节点
} return t>=m;
}
inline void subtask1() { //链
dfs(1,0); //for(int i=1;i<n;i++) printf("%d ",a[i]); putchar('\n');
int l=1,r=1e9,ans;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1; //二分答案
} printf("%d\n",ans);
}
int main(){
n=read(),m=read();
for(int i=1;i<n;i++) {
int x=read(),y=read(),z=read();
G[x].push_back(make_pair(y,z));
G[y].push_back(make_pair(x,z));
if(y!=x+1) chain=1;
} //dfs(1,0);
if(!chain) subtask1();
return 0;
}
算法二
你发现有 \(m=1\) 的部分分。
此时就是 树的直径 。
树的直径定义:
一棵树上长度最长的一条简单路径。
众所周知,树的直径只需要两次 \(\texttt{dfs}\).
即从任意一个点走到离它最远的点 \(x\),再从 \(x\) 走到它最远的点 \(y\).
那么 \(y \gets x\) 的这条路径就是直径。定义略,读者可自行学习。
敲模板即可。
时间复杂度:\(O(n)\).
实际得分:\(40pts\).
算法三
仍然注意到,\(a_i=1\) 的菊花图部分分。
对于菊花图,其实就是贪心。
你把所有边从大到小排序,然后肯定是选最大的 \(m\) 个。
那么,怎么配对呢?你发现,每条链要么单独,要么和别人拼起来,两条链作为一条链。
显然,能拼接的我们就拼。
所以,应将第 \(i\) 大的和第 \(i\) 小的配对,这样尽量平均。
这个贪心就不证明了吧。
时间复杂度:\(O(n + m)\).
实际得分:\(55pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,a[N],dis[N];
vector<pair<int,int> >G[N];
bool chain=0; //链的部分分 20
bool flower; //菊花图的部分分 15
inline void dfs_get(int dep,int bs) {
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i].first,y=G[dep][i].second;
if(x==bs) continue;
dfs_get(x,dep); a[dep]=y;
}
}
inline bool check(int dep) {
int t=0,sum=0;
for(int i=1;i<n;i++) {
if(sum+a[i]>=dep) sum=0,t++;
else sum+=a[i];
} return t>=m;
}
inline void subtask1() { //链
dfs_get(1,0);
int l=1,r=1e9,ans;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
} printf("%d\n",ans);
exit(0);
}
inline void dfs(int dep,int bs) {
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i].first,y=G[dep][i].second;
if(x==bs) continue;
dis[x]=dis[dep]+y; dfs(x,dep);
}
}
inline int get_far(int x) { //找到离 x 最远的点,求直径
memset(dis,0,sizeof(dis));
dfs(x,0); int maxi=0,maxh;
for(int i=1;i<=n;i++)
if(dis[i]>maxi) maxi=dis[i],maxh=i;
return maxh;
}
inline void dfs_get_2() {
for(int i=0;i<G[1].size();i++) {
int x=G[1][i].first,y=G[1][i].second;
a[x-1]=y;
}
}
int main(){
n=read(),m=read();
for(int i=1;i<n;i++) {
int x=read(),y=read(),z=read();
G[x].push_back(make_pair(y,z));
G[y].push_back(make_pair(x,z));
if(y!=x+1) chain=1;
if(x!=1) flower=1;
} if(!chain) subtask1(); //链
if(m==1) {
int t=get_far(1);
t=get_far(t);
printf("%d\n",dis[t]); return 0;
} //直径
if(!flower) {
dfs_get_2(); int ans=1e9;
sort(a+1,a+n); reverse(a+1,a+n);
for(int i=1;i<=m;i++) ans=min(ans,a[i]+a[(m<<1)-i+1]);
printf("%d\n",ans);
} //菊花图
return 0;
}
算法四
其实呢,分支不超过 \(3\) 可以认为是正解的弱化版。
因为正解的复杂度和分支数有一定关联。
首先我们二分答案,下面需要用 \(O(n)\) 或 \(O(n \log n)\) 的时间验证一个数 \(mid\)。
对于一条超过 \(mid\) 的链,直接扔掉,计入答案。
否则,我们叫它 “半链”。
一条半链,要么继承它的父节点,要么就和另一个半链拼在一起,要么就抛弃。
那么,对于以 \(i\) 为根的子树,所有的半链要两两配对(也可以抛弃一部分)使得 \(\geq mid\).
这里的半链是所有的 \(f_k (k \in \texttt{son(i)})\),即所有的儿子节点的半链。
这时候我们用到了排序,贪心,双指针。
首先从小到大排序,然后用双指针贪心即可。
对于剩下的半链,在剩下的半链中取最大的继承父节点,作为一个新的半链,等待上面的半链处理。
这里我们需要一个数据结构,维护这些半链。
显然,如果排序的话,时间复杂度将会达到 \(O(n k \log k)\). 其中 \(k\) 为每个节点儿子个数的和,接近于 \(n\)。
所以会被卡到 \(O(n^2 \log n)\).
这样只能得到 \(85pts\).
我们想到了 \(\texttt{mutilset}\).
维护之后,复杂度从 \(O(n^2 \log n)\) 降到了 \(O(n \log^2 n)\).(最坏)
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,a[N],dis[N];
vector<pair<int,int> >G[N];
bool chain=0; //链的部分分 20
bool flower; //菊花图的部分分 15
int dist[N],sum=0;
multiset<int>s; //正解
inline void dfs_get(int dep,int bs) {
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i].first,y=G[dep][i].second;
if(x==bs) continue;
dfs_get(x,dep); a[dep]=y;
}
}
inline bool check(int dep) {
int t=0,sum=0;
for(int i=1;i<n;i++) {
if(sum+a[i]>=dep) sum=0,t++;
else sum+=a[i];
} return t>=m;
}
inline void subtask1() { //链
dfs_get(1,0);
int l=1,r=1e9,ans;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
} printf("%d\n",ans);
exit(0);
}
inline void dfs__(int dep,int bs) {
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i].first,y=G[dep][i].second;
if(x==bs) continue;
dis[x]=dis[dep]+y; dfs__(x,dep);
}
}
inline int get_far(int x) { //找到离 x 最远的点,求直径
memset(dis,0,sizeof(dis));
dfs__(x,0); int maxi=0,maxh;
for(int i=1;i<=n;i++)
if(dis[i]>maxi) maxi=dis[i],maxh=i;
return maxh;
}
inline void dfs_get_2() {
for(int i=0;i<G[1].size();i++) {
int x=G[1][i].first,y=G[1][i].second;
a[x-1]=y;
}
}
inline int dfs(int x,int fa,int mid) {
//二分答案中的检验
int tot=0;
for(int i=0;i<G[x].size();i++) {
int u=G[x][i].first,v=G[x][i].second;
if(u==fa) continue;
tot+=dfs(u,x,mid);
if(tot>=m) return tot;
} dist[x]=0; s.clear();
for(int i=0;i<G[x].size();i++) {
int u=G[x][i].first,v=G[x][i].second;
if(u==fa) continue;
if(dist[u]+v>=mid) tot++;
else s.insert(dist[u]+v);
}
for(multiset<int>::iterator i=s.begin();i!=s.end() && s.size();) {
//双指针
multiset<int>::iterator nxt=s.lower_bound(mid-(*i));
if(nxt==i && nxt!=s.end()) nxt++;
if(nxt==s.end()) {i++;continue;}
s.erase(nxt),s.erase(i++); tot++;
if(tot>=m) return tot;
} if(!s.empty()) dist[x]=*s.rbegin();
return tot;
}
int main(){
n=read(),m=read();
for(int i=1;i<n;i++) {
int x=read(),y=read(),z=read();
G[x].push_back(make_pair(y,z));
G[y].push_back(make_pair(x,z));
if(y!=x+1) chain=1;
if(x!=1) flower=1;
} if(!chain) subtask1(); //链
if(m==1) {
int t=get_far(1);
t=get_far(t);
printf("%d\n",dis[t]); return 0;
} //直径
if(!flower) {
dfs_get_2(); int ans=1e9;
sort(a+1,a+n); reverse(a+1,a+n);
for(int i=1;i<=m;i++) ans=min(ans,a[i]+a[(m<<1)-i+1]);
printf("%d\n",ans); return 0;
} //菊花图
int l=1,r=5e9,ans;
while(l<=r) {
int mid=(l+r)>>1;
if(dfs(1,1,mid)>=m) l=mid+1,ans=mid;
else r=mid-1;
} printf("%d\n",ans);
return 0;
}
P5021 赛道修建 题解的更多相关文章
- 二分答案 + multiset || NOIP 2018 D1 T3 || Luogu P5021 赛道修建
题面:P5021 赛道修建 题解:二分答案,用Dfs进行判断,multiset维护. Dfs(x,fa,Lim)用来计算以x为根的子树中有多少符合条件的路径,并返回剩余未使用的最长路径长. 贪心思想很 ...
- 【题解】 P5021赛道修建
[题解]P5021 赛道修建 二分加贪心,轻松拿省一(我没有QAQ) 题干有提示: 输出格式: 输出共一行,包含一个整数,表示长度最小的赛道长度的最大值. 注意到没,最小的最大值,还要多明显? 那么我 ...
- P5021 赛道修建[贪心+二分]
题目描述 C 城将要举办一系列的赛车比赛.在比赛前,需要在城内修建 mm 条赛道. C 城一共有 nn 个路口,这些路口编号为 1,2,-,n1,2,-,n,有 n-1n−1 条适合于修建赛道的双向通 ...
- [NOIp2018] luogu P5021 赛道修建
我同学的歌 题目描述 你有一棵树,每条边都有权值 did_idi.现在要修建 mmm 条赛道,一条赛道是一条连贯的链,且一条边至多出现在一条赛道里.一条赛道的长被定义为,组成这条赛道的边的权值之和. ...
- P5021 赛道修建 (NOIP2018)
传送门 考场上把暴力都打满了,结果文件输入输出写错了.... 当时时间很充裕,如果认真想想正解是可以想出来的.. 问你 长度最小的赛道长度的最大值 显然二分答案 考虑如何判断是否可行 显然对于一个节点 ...
- 洛谷P5021 赛道修建
题目 首先考虑二分,然后发现最小长度越大的话,赛道就越少.所以可以用最终的赛道个数来判断长度是否合理.问题转化为给定一个长度,问最多有多少条互不重叠路径比这个给定长度大. 考虑贪心,毕竟贪心也是二分c ...
- 洛谷P5021 赛道修建 NOIp2018 贪心+二分答案
正解:贪心+LCA+二分答案 解题报告: 想先港下部分分qwq因为我部分分只拿到了10ptsQAQ(时间不够不是理由,其实还是太弱,所以要想很久,所以才时间不够QAQ m=1 找直径长度,完 一条链 ...
- 竞赛题解 - NOIP2018 赛道修建
\(\mathcal {NOIP2018}\) 赛道修建 - 竞赛题解 额--考试的时候大概猜到正解,但是时间不够了,不敢写,就写了骗分QwQ 现在把坑填好了~ 题目 (Copy from 洛谷) 题 ...
- 题解 NOIP2018【赛道修建】—— 洛谷
这道题有一点点树上dp的意思(大佬轻喷 我刚拿到这道题的时候毫无头绪,只知道这道题要二分答案 为什么是二分答案??? 题目: 目前赛道修建的方案尚未确定.你的任务是设计一 种赛道修建的方案,使得修建的 ...
随机推荐
- ant:如何用ant将web project打包成war包
说明:src中的文件将不会呈现出来,诸位可以自己写一个简单的web项目,然后依照我所提供的ant脚本配置来设置. 文件结构如图所示: 配置为下: build.xml < ...
- 机器CPU load过高问题排查
load average的概念 系统平均负载定义:在特定时间间隔内运行队列中(在CPU上运行或者等待运行多少进程)的平均进程数.如果一个进程满足以下条件则其就会位于运行队列中: 它没有在等待I/O操作 ...
- Leetcode 981. Time Based Key-Value Store(二分查找)
题目来源:https://leetcode.com/problems/time-based-key-value-store/description/ 标记难度:Medium 提交次数:1/1 代码效率 ...
- Oracle中的列转行实现字段拼接用例
文章目录 Oracle中的列转行实现字段拼接 场景 在SQL使用过程中经常有这种需求:将某列字段拼接成in('XX','XX','XX','XX','XX','XX' ...)做为查询条件. 实现 s ...
- 你的胃能Hold住未来的食物吗?
如果你是一名美食客,那么一定会发现现在越来越多的食物已经发生了翻天覆地的变化,很多食物正在以我们未知的形式出现在生活中,其中最大的莫过于分子美食.你想过吗?当食物发生改变的时候,你的胃是否能够Ho ...
- 基于FPGA的RGB图像转灰度图像算法实现
一.前言 最近学习牟新刚编著<基于FPGA的数字图像处理原理及应用>的第六章直方图操作,由于需要将捕获的图像转换为灰度图像,因此在之前代码的基础上加入了RGB图像转灰度图像的算法实现. 2 ...
- 致敬尤雨溪,Vue.js 让我赚到了第一桶金
最近这个 Vue.js 纪录片在前端圈广为传播,相信不少人已经看过了.第一次看编程领域的纪录片,感觉还挺新鲜的.这部 30 分钟左右的纪录片制作精良,主角是 Vue.js 作者尤雨溪,还穿插采访了框架 ...
- shell 之 case。。。esac多分支选择
case分支属于匹配执行的方式,它针对指定的变量预先设置一个可能的取值,判断该变量的实际取值是否与预设的某一个值相匹配,如果匹配上了,就执行相应的一组操作,如果没有任何值能够匹配,就执行预先设置的默认 ...
- jdbc对 数据库的数据进行增删改(两个类)
1.方法类 package com.com; import java.sql.Connection;import java.sql.DriverManager;import java.sql.Resu ...
- JavaScript的数组系列
数组 今天逆战班的学习主题关于Javascript的数组,主要有数组的概念.创建.分类.方法.遍历.经典算法...... 一.数组是什么呢?怎么写数组呢?数组有多少种呢? 数组的概念 对象是属性的无序 ...