[NOIp2018提高組]賽道修建
題目大意:
給你一棵\(n(n\le5\times10^4)\)個結點的樹,從中找出\(m\)個沒有公共邊的路徑,使得第\(m\)長的路徑最長。問第\(m\)長的路徑最長可以是多少。
思路:
二分答案+樹形DP。\(f[x]\)表示以\(x\)為根的子樹中最多能找出幾個長度\(\ge k\)的路徑。\(g[x]\)表示去掉已經滿足的路徑,從\(x\)子樹內往上連的最長的路徑有多長。
轉移時將所有子結點的貢獻\(g[y]+w\)排序。若貢獻已經\(\ge k\),那么就直接計入答案。否則從小到大枚舉每一個貢獻,找到能與其配對的最小的貢獻,計入答案。如果找不到能與之配對的貢獻,那么就用它來更新\(g[x]\)。可以證明這樣能夠在保證\(f[x]\)最大化的情況下,最大化\(g[x]\)。
時間復雜度\(\mathcal O(n\log n\log\)值域\()\)。
源代碼:
#include<set>
#include<cstdio>
#include<cctype>
#include<vector>
#include<climits>
#include<algorithm>
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'0';
while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
return x;
}
const int N=5e4+1;
struct Edge {
int to,w;
};
std::vector<Edge> e[N];
inline void add_edge(const int &u,const int &v,const int &w) {
e[u].push_back((Edge){v,w});
e[v].push_back((Edge){u,w});
}
std::multiset<int> t;
int f[N],g[N],len;
void dfs(const int &x,const int &par) {
f[x]=0;
for(auto &j:e[x]) {
const int &y=j.to;
if(y==par) continue;
dfs(y,x);
f[x]+=f[y];
}
for(auto &j:e[x]) {
const int &y=j.to;
if(y==par) continue;
t.insert(g[y]+j.w);
}
while(!t.empty()) {
const int u=*t.rbegin();
if(u>=len) {
f[x]++;
t.erase(t.find(u));
} else {
break;
}
}
g[x]=0;
while(!t.empty()) {
const int u=*t.begin();
t.erase(t.begin());
auto p=t.lower_bound(len-u);
if(p==t.end()) {
g[x]=u;
} else {
t.erase(p);
f[x]++;
}
}
t.clear();
}
inline int calc(const int &k) {
len=k;
dfs(1,0);
return f[1];
}
int main() {
const int n=getint(),m=getint();
int l=INT_MAX,r=0;
for(register int i=1;i<n;i++) {
const int u=getint(),v=getint(),w=getint();
add_edge(u,v,w);
l=std::min(l,w);
r+=w;
}
while(l<=r) {
const int mid=(l+r)>>1;
if(calc(mid)>=m) {
l=mid+1;
} else {
r=mid-1;
}
}
printf("%d\n",l-1);
return 0;
}