竞赛题解 - NOIP2018 赛道修建
赛道修建 - 竞赛题解
额……考试的时候大概猜到正解,但是时间不够了,不敢写,就写了骗分QwQ
现在把坑填好了~
题目
(Copy from 洛谷)
题目描述
C 城将要举办一系列的赛车比赛。在比赛前,需要在城内修建
C 城一共有
一条赛道是一组互不相同的道路
目前赛道修建的方案尚未确定。你的任务是设计一种赛道修建的方案,使得修建的
输入输出格式
输入格式:
输入文件第一行包含两个由空格分隔的正整数
接下来
输出格式:
输出共一行,包含一个整数,表示长度最小的赛道长度的最大值。
解析
首先题目非常重要——这是一个树形结构,而且满足单调性(即如果答案为x,则>x的值都不能符合条件,<x的值都符合条件)。
那么根据单调性就可以二分了,还是比较容易想到二分答案。然后就是检验的问题了……
如果大家做了“菊花图”(就是从一个顶点伸出去很多条边)的部分数据,就应该知道有一个贪心的性质——对于一个点u,如果连接 u 和 u的一个儿子 的边的长度大于等于答案,那就把这条边割开,单独形成一条路径;接下来考虑长度比答案小的边,那么我们尽量地将长度大的边与较小边匹配形成一条路径。这样的话我们就可以写出来一段代码(AC的):
Copy//edg[]是u与u的儿子的边的长度(都不超过二分出来的答案)
sort(edg,edg+n);
int res=0;
for(int l=0,r=n;l<r;r--){
while(l<r && edg[l]+edg[r]<ans) l++;
if(l>=r) break;
res++; //新构成的路径
l++;
}
我们不妨考虑每一个点 u 及其儿子 v[] ——形成路径的情况无非是①加上u->v[i]的边形成一个路径;②加上u->v[i]和u->v[j]的边形成一个路径;③加上u->v[i]的边然后再与u的祖先相连。
不难想到我们可以把每棵子树都看成一个菊花图做一次贪心。
但是有上述的情况③,这样的话就不能直接看边权了,我们可以让 dp[u] 表示在点u的子树以u为端点的路径的长度。然后考虑子树u中u的一个儿子v,如果 dp[v]+(u->v的边长) 满足答案,就将已经构成的路径个数cnt++;否则将 dp[v]+(u->v的边长) 储存到use[u]中。最后把use[u]中的边按菊花图的方法做一次贪心,就可以得到能够构成的路径条数的最大值:
Copyfor(int l=0,r=use[u].size()-1;r>=0;r--){
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l>=r) break;
mat++;l++;
}
fans[u]+=mat; //储存答案
贪心的想,在这里一定要尽可能多的构成路径,但是在这个条件下将尽可能长的一个没必要匹配的路径留下来继续向u的祖先连接;很明显这也是存在单调性的——如果选择一条长度为X的路径“留下来”可以得到最大构成的路径数,那么长度小于X的路径就一定也可以得到最大路径数。
Copywhile(l+1<r){
int mid=(l+r)>>1;
if(Check(u,mid,use[u].size(),num)==mat) l=mid;
else r=mid;
}
dp[u]=use[u][l];
这样就可以检验了,即看能够得到的路径条数是否能够满足要求。
(不知道大家看懂没啊……有什么问题的话还是直接在文末的作者邮箱里面问吧QwQ)
源代码
Copy/*Lucky_Glass*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50000;
struct EDGE{
int to,nxt;ll dis;
EDGE(){}
EDGE(int _to,int _nxt,ll _dis):to(_to),nxt(_nxt),dis(_dis){}
}edg[N*2+5];
int adj[N+5],edgtot;
void AddEdge(int u,int v,ll dis){
edg[++edgtot]=EDGE(v,adj[u],dis);
adj[u]=edgtot;
}
int n,m;
vector< int > use[N+5];
int dp[N+5],fans[N+5];
int Check(int u,int tag,int siz,ll num){
int res=0;
for(int l=0,r=siz-1;r;r--){
if(r==tag) r--;
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l==tag) l++;
if(l>=r) break;
res++;l++;
}
return res;
}
void DFS(int u,int pre,ll num){
dp[u]=fans[u]=0;use[u].clear();
for(int i=adj[u];i;i=edg[i].nxt){
int v=edg[i].to;ll dis=edg[i].dis;
if(v==pre) continue;
DFS(v,u,num);
dp[v]+=dis;
if(dp[v]>=num) fans[u]++;
else use[u].push_back(dp[v]);
}
sort(use[u].begin(),use[u].end());
int mat=0;
for(int l=0,r=use[u].size()-1;r>=0;r--){
while(l<r && use[u][l]+use[u][r]<num) l++;
if(l>=r) break;
mat++;l++;
}
fans[u]+=mat;
if(mat*2==use[u].size()) return;
int l=0,r=use[u].size();
while(l+1<r){
int mid=(l+r)>>1;
if(Check(u,mid,use[u].size(),num)==mat) l=mid;
else r=mid;
}
dp[u]=use[u][l];
}
bool Check(ll num){
DFS(1,-1,num);
int res=0;
for(int i=1;i<=n;i++)
res+=fans[i];
return res>=m;
}
int main(){
scanf("%d%d",&n,&m);
ll l=0,r=0;
for(int i=1;i<n;i++){
int u,v;ll dis;scanf("%d%d%lld",&u,&v,&dis);
AddEdge(u,v,dis);AddEdge(v,u,dis);
r+=dis;
}
r/=1ll*m;
while(l+1<r){
ll mid=(l+r)>>1;
if(Check(mid)) l=mid;
else r=mid;
}
if(Check(r)) printf("%lld\n",r);
else printf("%lld\n",l);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现