【NOIP2018】赛道修建(正解)
NOIP题目怎么都好长
使用m条各自无边重叠的边覆盖一棵树的一部分
要求最大化这些边的最短长度
考场上高性价比的做法:点我
最大化最短,这种一看就是二分答案啦……
参考上面的高性价比部分分做法,我们可以把可用的边分为两类:
一类是连接到当前子树的根的路径长度\(Dis[u]\)
一类是当前子树中的路径\(ans[u]\)
在一个子树中,我们可以把短的路径尽可能合并成长路径来统计答案
于是只需要两遍二分即可
代码:
#include<bits/stdc++.h>
#define N 50005
using namespace std;
int n,m,u,v,w;
struct Edge
{
int next,to,dis;
}edge[N<<1];
int cnt=0,head[N];
inline void add_edge(int from,int to,int dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}
template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
bool vis[N];
int fa[N],len[N],Dis[N],ans[N];
int cal(int num,int limit)
{
int p=1,res=0;
for(register int i=num;i>p;--i)
{
if(vis[i]) continue;
while(p<i&&(len[p]+len[i]<limit||vis[p])) p++;
if(p<i&&!vis[p]&&len[p]+len[i]>=limit) res++;
p++;
}
return res;
}
void dfs(int u,int limit)
{
ans[u]=0;
for(register int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa[u]) continue;
fa[v]=u;
Dis[v]=edge[i].dis;
dfs(v,limit);
ans[u]+=ans[v];
}
int num=0;
for(register int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa[u]) continue;
len[++num]=Dis[v];
}
if(num)
{
sort(len+1,len+num+1);
len[0]=0;
int w=cal(num,limit);
ans[u]+=w;
int L=0,R=num,res;
while(L<=R)
{
int Mid=(L+R)>>1;
vis[Mid]=1;
if(cal(num,limit)==w) res=Mid,L=Mid+1;
else R=Mid-1;
vis[Mid]=0;
}
Dis[u]+=len[res];
}
if(Dis[u]>=limit) Dis[u]=0,ans[u]++;
}
bool check(int x)
{
// cout<<"now is checking: "<<x<<endl;
memset(ans,0,sizeof(ans));
memset(Dis,0,sizeof(Dis));
int num=0;
dfs(1,x);
if(ans[1]>=m) return true;
return false;
}
int main()
{
int l=1,r=0;
read(n);read(m);
for(register int i=1;i<=n-1;++i)
{
read(u);read(v);read(w);
add_edge(u,v,w);
add_edge(v,u,w);
r+=w;
}
int 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);
return 0;
}
$$\text{这里是tqr,联系我请加QQ735748368}$$