P5021 赛道修建
分析:
很明显要二分一个值。
对于一条赛道与一个点u的关系,可以分成三种情况:
1.完全在一棵u的子树内。 2.一半在子树内,一半在子树外 3. 经过u,连向子树内的另一条链
对于第一种情况,直接在递归下去的时候就计入贡献。
对于第二三种情况,开一个multiset,遇到不合法的值就放入multiset中。
每次回溯的时候,先将子树内可以两两合并的合并了,不能两两合并的,取一个最大值留给其父亲节点去匹配。
multiset有很多细节:
1. erase既可以删除一个值,也可以删除一个位置!!
2. 删除的顺序很重要,千万不要出现删掉一个点后,又指向那个点,这时候会指向空,从而导致崩溃。
#include<bits/stdc++.h> using namespace std; #define ri register int #define N 50005 int head[N],nex[N<<1],to[N<<1],tot=0,w[N<<1],n,m,mid,ans; void add(int a,int b,int ww) { tot++; to[tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww; } multiset<int> st[N]; multiset<int>::iterator it;//!!!在外面定义才能遍历完 int dfs(int u,int ff) { st[u].clear(); int val; for(ri i=head[u];i;i=nex[i]){ int v=to[i]; if(v==ff) continue; val=dfs(v,u)+w[i]; if(val>=mid) ans++;//子树内的链直接满足情况,就ans++ else st[u].insert(val); } int mx=0; while(!st[u].empty()){ if(st[u].size()==1) return mx=max(mx,*st[u].begin()); it=st[u].lower_bound(mid-*st[u].begin());//找到对应的位置 if(st[u].begin()==it && st[u].count(*it)==1) it++;//如果找到自己,就跳过 //如果不存在对应的值与它配对,说明它太小了,就删掉这种值 if(it==st[u].end()) mx=max(mx,*st[u].begin()),st[u].erase(*st[u].begin());//注意删除的是值!! else{//否则就将其与对应位置配对 ans++; //注意顺序:先删it,否则删去begin后,若it指向begin,那么it将指向空,会爆掉 st[u].erase(it); st[u].erase(st[u].begin());//这里删除的是头位置,而不应该是值,因为值有多个,位置只有一个 } } return mx; } int main() { scanf("%d%d",&n,&m); int sum=0,a,b,ww; for(ri i=1;i<=n-1;++i) scanf("%d%d%d",&a,&b,&ww),add(a,b,ww),add(b,a,ww),sum+=ww; int l=0,r=sum+1,anss=0; while(l<r){ mid=(l+r)>>1; ans=0; dfs(1,0); if(ans<m) r=mid; else l=mid+1,anss=mid; } printf("%d\n",anss); }