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);
}
View Code

 

 

posted on 2019-11-11 20:45  rua-rua-rua  阅读(165)  评论(0编辑  收藏  举报