NOIP 2018 提高组 复赛 第一天 第三题 赛道修建 track 5分代码(Floyd)+20分代码(菊花图,树的直径,链(二分))
总目录详见:NOIP 提高组 复赛 试题 目录 信奥 历年
在线测评地址:https://www.luogu.com.cn/problem/P5021
1.5分代码(floyd算法):
针对测试点1进行编写
发现是先用Floyd算法,在树上计算最短路径,再寻找最短路径中的最大距离。
5分代码如下
#include <stdio.h>
#define INF 500000010
int n,m;
int map[110][110];
int main(){
int i,j,k,a,b,l,mx;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)//map初始化
for(j=1;j<=n;j++)
if(i==j)map[i][j]=0;
else map[i][j]=INF;
for(i=1;i<n;i++){
scanf("%d%d%d",&a,&b,&l);
map[a][b]=l,map[b][a]=l;
}
for(k=1;k<=n;k++)//Floyd算法
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(map[i][j]>map[i][k]+map[k][j])
map[i][j]=map[i][k]+map[k][j];
mx=0;
for(i=1;i<=n;i++)//寻找最大距离
for(j=1;j<=n;j++)
if(map[i][j]!=INF&&mx<map[i][j])
mx=map[i][j];
printf("%d\n",mx);
return 0;
}
2.20代码(ai=1的情况(菊花图))
解法:把所有边权记录下来,从大到小排序。设边权为 w,答案即为 w[1]+w[2m],w[2]+w[2m-1],...,w[m]+w[m+1]的最小值,时间复杂度 O(nlogn),
该算法的难点在于,若w数组长度太短,即n-1<2*m的处理上,可以将数组加长,自大到小排序后,多出的部分,用0补齐。
20分 菊花图 代码如下
#include <cstdio>
#include <algorithm>
#define maxn 50010
using namespace std;
int n,m,head[maxn],tot;
struct node{
int to,next,w;
}e[maxn<<1];
void add_edge(int u,int v,int w){
tot++,e[tot].to=v,e[tot].next=head[u],e[tot].w=w,head[u]=tot;
}
int cmp(node a,node b){
return a.w>b.w;
}
int main(){
int u,v,w,i,a1,ans=2000000000;
scanf("%d%d",&n,&m);
a1=1;
for(i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
if(u!=1)a1=0;
add_edge(u,v,w);
add_edge(v,u,w);
}
if(a1==1){
for(i=1;i<n;i++)e[i*2-1].w=0;//让2*(n-1)中的n-1部分数据为0
sort(e+1,e+2*n-1,cmp);//自大到小排序
for(i=1;i<=m;i++)ans=min(ans,e[i].w+e[2*m-i+1].w);
printf("%d\n",ans);
}
return 0;
}
3.20代码(树的直径)
该项讨论m==1的情况。
20分 树的直径 代码如下
#include <stdio.h>
#define maxn 50010
int n,m,head[maxn],tot,ans,d[maxn],f_num;
struct node{
int to,next,w;
}e[maxn<<1];
void add_edge(int u,int v,int w){
tot++,e[tot].to=v,e[tot].next=head[u],e[tot].w=w,head[u]=tot;
}
void dfs(int u,int fa){
int b,v,w;
if(ans<d[u])ans=d[u],f_num=u;
for(b=head[u];b;b=e[b].next){
v=e[b].to,w=e[b].w;
if(v==fa)continue;
d[v]=d[u]+w;
dfs(v,u);
}
}
int main(){
int u,v,w,i;
scanf("%d%d",&n,&m);
for(i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
if(m==1){
ans=0,d[1]=0;
dfs(1,0);
ans=0,d[f_num]=0;
dfs(f_num,0);
printf("%d\n",ans);
}
return 0;
}
4.20分代码(bi=ai+1 的情况(一条链))
解法:把所有边权记录下来,这种情况等价于将序列分割成 m 段,使 m 段区间和的最小值最大。
那么二分 m 段区间和的最小值,然后 O(n)贪心扫一遍,时间复杂度 O(nlogn)
该做法的难点是,读入数据顺序不定,需重新深搜遍历,读出相应的链式结构。
20分 链 代码如下
#include <cstdio>
#include <algorithm>
#define maxn 50010
using namespace std;
int n,m,head[maxn],tot,sum,a[maxn];
struct node{
int to,next,w;
}e[maxn<<1];
void add_edge(int u,int v,int w){
tot++,e[tot].to=v,e[tot].next=head[u],e[tot].w=w,head[u]=tot;
}
void dfs(int u,int fa){//找回链式结构
int b,v;
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(v==fa)continue;
a[u]=e[b].w;
dfs(v,u);
}
}
int judge(int x){
int now,t,i;
now=0,t=0;
for(i=1;i<n;i++){
if(now+a[i]>=x)t++,now=0;
else now+=a[i];
}
return t>=m;
}
void solve(){
int l,r,mid;
l=1,r=sum+1;
while(l+1<r){
mid=(l+r)/2;
if(judge(mid))l=mid;
else r=mid;
}
printf("%d\n",l);
}
int main(){
int u,v,w,i,lian=1;
scanf("%d%d",&n,&m);
for(i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
if(v!=u+1)lian=0;
add_edge(u,v,w);
add_edge(v,u,w);
sum+=w;
}
if(lian==1){
dfs(1,0);
solve();
}
return 0;
}