做法:贪心/二分/( exttt{DFS})(树形 ( exttt{DP}))/并查集
前言
清一色的 ( exttt{multiset}) ,为什么啊?
这的确是一道好题,做法很多,对于这里贪心的维护难道非要经行删除操作?(还有为什么有人说显然要 ( exttt{multiset}) ?)像我这样的蒟蒻不会 ( exttt{multiset}) 怎么办?
提供一种不用 ( exttt{multiset}) 的做法(码量小)。
题意
给你一颗带权树,要你选 (k) 条没有重边的链,使得这些链中权值最小值最大。
这里链的权值定义为链上所有边权之和。
思路
好像做过一道类似的题
( exttt{undate}):确实为( exttt{P6147})的加强版。
看到最小值最大,显然二分答案,且有十分明显的单调性,然后 (check) 一下就好了。
(check) 里面是一个经典的树形 ( exttt{DP}),考虑从叶子节点往上处理,因为对于每个节点向上到父亲的边只有一条,所以对于每个节点记录从这个节点一直往下最深并没有选进链的链(以下记为 (s[i]) ,如何得到这个值请往下看)。
对于每一个节点,我们要进行两种操作。
-
若以这个节点为链的端点,有大于目标值的链,直接对答案贡献 (+1) 。
-
若以这个节点为某一条链,两个端点的 ( exttt{LCA}) ,则要做一个拼接操作,将两个儿子的 (s[i]+dis(fa,son_i)) 值合并,但这两个 (s) 值相加必须要大于目标值。
剩下的儿子节点中最大的 (s[i]+dis(fa,son_i)) 作为 (s[fa]) 。
可以证明,先要优先最大化合并操作的次数,因为若要优先最大化 (s[i]) ,则可能这第 (i) 个节点的合并个数会变小,但 (s[i]) 增大最多对i的父节点的贡献 (+1) 。(贪心*1)
但是在优先最大化每个节点合并的次数,还要次优先最大化 (s[i]) ,原本我有一个错误的贪心:是先排序 (s[son\_of\_i]) ,然后不断将最左边的数和最右边的数合并,若不能合并,则将最左边的值删去。
但是这个贪心是错的,参考样例:2 8 9 目标值:10 wrong:s[n]=8 ans:s[n]=9
。
发现这里疏忽了一点,就是应该选择这个数最小能匹配的数,可以用 ( exttt{lower\_bound}) 求出。(贪心*2)
求出那个数之后,要将它删去,这里我用 (Large exttt{并查集}) 来维护,若第 (i) 个数被删去,就将自己的父亲定为 (i+1) 。然后取二分到的那个数的祖宗,若这个祖宗是 (s.size()+1) (即这个数后面的数都被删完了),就跳过这个数。
时间复杂度 (O((logsum val) imes Nlog N imesalpha(N))) (远远达不到的,本人不会均摊QwQ)
代码
#include <bits/stdc++.h>
using namespace std;
#define PB push_back
#define MP make_pair
const int N = 5e4;
#define pii pair<int,int>
inline int read()
{
int s = 0;
register bool neg = 0;
register char c = getchar();
for (; c < '0' || c > '9'; c = getchar())
neg |= (c == '-');
for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
;
s = (neg ? -s : s);
return s;
}
int a,b,p,res,s[N+10],nxt[N+10],lst[N+10],Fa[N+10];
bool vis[N+10];
vector<pair<int,int> >st[N+10];
inline int f(int n) {
return Fa[n]==n?n:Fa[n]=f(Fa[n]);
}
inline void dfs(int n,int fa) {
s[n]=0;
vector<int> q;
q.clear();
for(int i=0;i<st[n].size();i++) {
int v=st[n][i].first,val=st[n][i].second;
if(v==fa) continue;
dfs(v,n);
if(s[v]+val>=p) res++;
else q.PB(val+s[v]);
}
if(!q.size()) return;
if(q.size()==1) {
s[n]=q[0];
return;
}
sort(q.begin(),q.end());
for(int i=0;i<=(int)q.size();i++) Fa[i]=i,vis[i]=0;
for(int i=0;i<(int)(q.size()-1);i++) {
if(vis[i]) continue;
if(i>=q.size()-1||i==-1) break;
int t=lower_bound(q.begin()+i+1,q.end(),p-q[i])-q.begin();
if(t>=q.size()) continue;
t=f(t);
if(t>=q.size()) continue;
if(q[t]+q[i]<p) continue;
vis[t]=1;
vis[i]=1;
Fa[t]=t+1;
res++;
}
for(int i=(int)(q.size()-1);i>=0;i--) if(!vis[i]) {
s[n]=q[i];
break;
}
}
inline bool check(int n) {
p=n;
res=0;
dfs(1,0);
return res>=b;
}
signed main()
{
// freopen("in.txt","r",stdin);
a=read();
b=read();
int x,y,z,mx=0;
for(int i=1;i<a;i++) {
x=read();
y=read();
z=read();
mx+=z;
st[x].PB(MP(y,z));
st[y].PB(MP(x,z));
}
int l=1,r=mx,ans=0;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
return 0;
}