Luogu5021 赛道修建
https://www.luogu.com.cn/problem/P5021
图论
十分简单的题目,想起NOIP2018时叹其为不可做题,一点一点啃暴力分(自闭)
很明显,需要二分答案,设二分到的值为\(mid\)
对于每个节点考虑,倘若有一条链经过节点\(u\)和其父亲之间的连边,那么我们只能有一条链延伸上去,在满足取得的链数最多的情况下,我们只需要让延伸上去的链最长即可,设延伸上去的最长链长为\(mxl_u\)
怎么操作?对于\(u\)的子节点\(v\),他们都存在一个\(mxl_v\),当然,\(mxl_v\)要加上\(edge_{u,v}\)之后进行操作,然后我们将这些数当成数列\(A\)
对于已经\(\ge mid\)的\(a_i\),直接单独增加答案,然后去掉即可
我们从小到大匹配,对于\(a_i\),如果有\(\ge mid-a_i\)的数,直接匹配满足条件的最小的那一个,答案增加,否则\(a_i\)作为\(mxl_u\)的候选
要支持动态删除,可以直接利用\(multiset\),然后就\(AC\)了
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
#define N 50005
using namespace std;
int n,m,x,y,z,tot,mxl[N],fr[N],nxt[N << 1],d1[N << 1],d2[N << 1];
int l=0,r=0,mid,ans=0,kz;
multiset<int>s;
void add(int x,int y,int z)
{
tot++;
d1[tot]=y;
d2[tot]=z;
nxt[tot]=fr[x];
fr[x]=tot;
}
void dfs(int u,int F)
{
for (int i=fr[u];i && kz>0;i=nxt[i])
{
int v=d1[i];
if (v==F)
continue;
if (!nxt[fr[v]])
{
mxl[v]=d2[i];
continue;
}
dfs(v,u);
mxl[v]+=d2[i];
}
if (kz<=0)
return;
s.clear();
mxl[u]=0;
for (int i=fr[u];i;i=nxt[i])
{
int v=d1[i];
if (v==F)
continue;
if (mxl[v]>=mid)
kz--; else
s.insert(mxl[v]);
}
while (!s.empty() && kz>0)
{
multiset<int> :: iterator it=s.begin();
int o=*it;
s.erase(it);
multiset<int> :: iterator it2=s.lower_bound(mid-o);
if (it2!=s.end())
{
kz--;
s.erase(it2);
continue;
}
mxl[u]=max(mxl[u],o);
}
}
bool check()
{
kz=m;
dfs(1,0);
if (mxl[1]>=mid)
kz--;
return kz<=0;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
r+=z;
}
r/=m;
while (l<=r)
{
mid=(l+r) >> 1;
if (check())
{
ans=mid;
l=mid+1;
} else
r=mid-1;
}
printf("%d\n",ans);
return 0;
}