树上路径

题目描述

给出一棵树,求出最小的k,使得,且在树中存在路径p,使得k>=S且k<=E。(k为路径p上的边的权值和)
n<=10^5,|E-S|<=10^6,1<=Wi<=1000,|E|,|S|<=10^9

怎么看都是点剖。

而对k有两个限制,不能直接求最小值,但是可以求在这个区间范围内的k有多少个。

二分+点剖

二分E的值,求在这个区间范围内的k的数量,若>0则合法。
只有第一次点剖才排序,然后保存排序后的数值,这样复杂度为O( nlog2n ),否则复杂度是O( nlog3n )

代码

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define ll long long
using namespace std;
const int maxn=100000+100;
int i,j,n,d[maxn],f[maxn],s[maxn],root,mid,ss,l,r,t,ro[maxn];
ll ans;
int q[maxn*50],st[maxn*50],en[maxn*50],tt;
int k[maxn],g[maxn*2],next[maxn*2],c[maxn*2],num,cnt;
int k1[maxn],g1[maxn*2],next1[maxn*2],b;
bool bz[maxn];
void add(int x,int y,int z){next[++num]=k[x];k[x]=num;g[num]=y;c[num]=z;}
void add1(int x,int y){next1[++cnt]=k1[x];k1[x]=cnt;g1[cnt]=y;}
void dfs(int x,int y){
    s[x]=1,f[x]=0;
    int i=k[x];
    while (i){
        if ((!bz[g[i]])&&(g[i]!=y)) dfs(g[i],x),s[x]+=s[g[i]],f[x]=max(f[x],s[g[i]]);
        i=next[i];
    }
    f[x]=max(f[x],f[0]-s[x]);if (f[x]<f[root]) root=x;
}
void df(int x,int y){
    if (d[x]>mid) return;
    q[++num]=d[x];
    int i=k[x];
    while (i){
        if ((!bz[g[i]])&&(g[i]!=y)) d[g[i]]=d[x]+c[i],df(g[i],x);
        i=next[i];
    }
}
ll chu(int x){
    tt++;
    if (st[tt]==0) {
        st[tt]=num+1,df(x,0),en[tt]=num;
        sort(q+st[tt],q+1+en[tt]);
    }
    int i,j1=en[tt],j=en[tt];ll w=0;
    fo(i,st[tt],en[tt]-1){
        while (q[i]+q[j]>mid&&j>=i) j--;if (j<=i) break;
        if (j1==i) j1=i+1;else
        while ((q[i]+q[j1-1]>=ss)&&(j1-1>i)) j1--;
        if ((q[i]+q[j1]<ss)||(j<j1)) continue;
        w+=j-j1+1;
    }
    return w;
}
void fen(int x){
    bz[x]=1,d[x]=0,b=0,ans+=chu(x);b=1;
    int i=k[x];
    while (i){
        if (!bz[g[i]]) add1(x,g[i]),ans-=chu(g[i]);
        i=next[i];
    }i=k1[x];
    while (i){
        int go=g1[i];
        f[root=0]=s[go],dfs(go,0),ro[++t]=root;
        fen(ro[t]);
        i=next1[i];
    }
}
void fen1(int x){
    if (ans) return;
    b=0,ans+=chu(x);
    int i=k1[x];b=1;
    while (i) {
        ans-=chu(g1[i]);
        i=next1[i];
    }
    i=k1[x];while (i)fen1(ro[++t]),i=next1[i];
}
int main(){
    scanf("%d%d%d",&n,&ss,&r);l=ss,mid=r;
    fo(i,1,n-1) {int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);add(y,x,z);}
    t=0;f[root=0]=n;
    dfs(1,0);ro[t]=root;num=tt=0;
    fen(root);
    if (ans==0) {printf("-1\n");return 0;}
    while (l<r){
        mid=(l+r)/2,t=tt=ans=0,fen1(ro[t]);
        if (ans) r=mid;else l=mid+1;
    }
    printf("%d\n",l);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值