bzoj2599 [IOI2011]Race 点分治

4 篇文章 0 订阅

Description


给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000

Solution


统计树上路径问题的一大利器就是点分治,然鹅我并不太熟练
这题直接用一个桶,b[x]记录权值和为x路径的最少边数,分治的时候统计一下经过当前重心的答案,即用b[k-dis[x]]+dep[x]更新答案
每次先查询再插入,最后枚举一边删除,这样可以既保证复杂度也可以保证不算重复

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

const int INF=0x3f3f3f3f;
const int N=400005;
const int E=2000005;

struct edge {int x,y,w,next;} e[N];

int ls[N],edCnt;
int size[N],max[N],bct[E];
int dis[N],dep[N];
int root,sum,ans,n,m;

bool vis[N];

void add_edge (int x,int y,int w) {
    e[++edCnt]=(edge) {x,y,w,ls[x]}; ls[x]=edCnt;
    e[++edCnt]=(edge) {y,x,w,ls[y]}; ls[y]=edCnt;
}

void get_root(int fa,int x) {
    size[x]=1; max[x]=0;
    for (int i=ls[x];i;i=e[i].next) {
        if (e[i].y==fa||vis[e[i].y]) continue;
        get_root(x,e[i].y);
        size[x]+=size[e[i].y];
        max[x]=std:: max(max[x],size[e[i].y]);
    }
    max[x]=std:: max(max[x],sum-size[x]);
    if (!root||max[x]<max[root]) root=x;
}

void calc(int fa,int x) {
    if (dis[x]<=m) ans=std:: min(ans,bct[m-dis[x]]+dep[x]);
    for (int i=ls[x];i;i=e[i].next) {
        if (e[i].y==fa||vis[e[i].y]) continue;
        dep[e[i].y]=dep[x]+1;
        dis[e[i].y]=dis[x]+e[i].w;
        calc(x,e[i].y);
    }
}

void add(int fa,int x,int opt) {
    if (dis[x]<=m) {
        if (opt) bct[dis[x]]=std:: min(bct[dis[x]],dep[x]);
        else bct[dis[x]]=INF;
    }
    for (int i=ls[x];i;i=e[i].next) {
        if (e[i].y==fa||vis[e[i].y]) continue;
        add(x,e[i].y,opt);
    }
}

void solve(int x) {
    vis[x]=1; bct[0]=0;
    for (int i=ls[x];i;i=e[i].next) {
        if (vis[e[i].y]) continue;
        dep[e[i].y]=1; dis[e[i].y]=e[i].w;
        calc(x,e[i].y);
        add(x,e[i].y,1);
    }
    for (int i=ls[x];i;i=e[i].next) {
        if (vis[e[i].y]) continue;
        add(x,e[i].y,0);
    }
    for (int i=ls[x];i;i=e[i].next) {
        if (vis[e[i].y]) continue;
        root=0; sum=size[e[i].y];
        get_root(x,e[i].y);
        solve(root);
    }
}

int main(void) {
    scanf("%d%d",&n,&m); ans=n;
    for(int i=1;i<=m;i++) bct[i]=n;
    rep(i,2,n) {
        int x,y,w; scanf("%d%d%d",&x,&y,&w);
        add_edge(++x,++y,w);
    }
    sum=n; root=0; get_root(0,1);
    solve(root);
    if (ans==n) ans=-1;
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值