bzoj2599: [IOI2011]Race 点分治

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

明显点分治,但是愚蠢的我并没有想到怎么分治。。%hzwer。
开一个t[i]表示整棵树中权值和为i的路径有多少条,那么我们分治每一颗树的时候,再算出子节点到当前根的dis[x]权值距离和d[x]点数距离,然后就可以直接更新了ans=min(ans,t[k-dis[x]]+d[x]);
然后再更新dis和d,因为如果先更新了就会算重,有一种情况,即起点终点在子树内但是经过了根,这样就会算重复了。。
注意inf设小一点,不然会爆int。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=4e5+5;
const int M=1e6+5;
int n,m;
const int inf=0x7fffffff/3;
int cnt,sum,root,ans,k;
int t[M],head[N],next[N],go[N],val[N];
int dis[N],d[N],son[N],f[N],tot;
bool vis[N];
inline void add(int x,int y,int z)
{
    go[++tot]=y;
    val[tot]=z;
    next[tot]=head[x];
    head[x]=tot;
}
inline void getroot(int x,int fa)
{
    son[x]=1;
    f[x]=0;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (!vis[v]&&v!=fa)
        {
            getroot(v,x);
            son[x]+=son[v];
            f[x]=max(f[x],son[v]);
        }
    }
    f[x]=max(f[x],sum-son[x]);
    if (f[x]<f[root])root=x;
}
inline void cal(int x,int fa)
{
    if (dis[x]<=k)ans=min(ans,d[x]+t[k-dis[x]]);
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa&&!vis[v])
        {
            d[v]=d[x]+1;
            dis[v]=dis[x]+val[i];
            cal(v,x);
        }
    }
}
inline void update(int x,int fa,bool flag)
{
    if (dis[x]<=k)
    {
        if (flag)t[dis[x]]=min(t[dis[x]],d[x]);
        else t[dis[x]]=inf;
    }
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa&&!vis[v])update(v,x,flag);
    }
}
inline void work(int x)
{
    vis[x]=1;
    t[0]=0;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (!vis[v])
        {
            d[v]=1;
            dis[v]=val[i];
            cal(v,0);
            update(v,0,1);
        }
    }
    for(int i=head[x];i;i=next[i])
    if (!vis[go[i]])update(go[i],0,0);
    for(int i=head[x];i;i=next[i])
    if (!vis[go[i]])
    {
        int v=go[i];
        root=0;
        sum=son[v];
        getroot(v,0);
        work(root);
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    fo(i,1,k)t[i]=inf/3;
    fo(i,1,n-1)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        x++,y++;
        add(x,y,z);
        add(y,x,z);
    }
    ans=sum=f[0]=n;
    getroot(1,0);
    work(root);
    if (ans!=n)printf("%d\n",ans);
    else printf("-1\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值