洛谷4149 BZO2599 IOI2011 race 点分治

题目链接

题意:
给你一棵 n n n个点的树,边有边权,求树上长度为 k k k的路径至少要经过几条边,如果没有符合条件的路径,输出-1。 n &lt; = 2 e 5 , k &lt; = 1 e 6 n&lt;=2e5,k&lt;=1e6 n<=2e5,k<=1e6

题解:
首先感谢DT_Kang的帮助!DT_Kang大佬帮我调了这个题,并且给我指出了我之前的点分治的复杂度都有问题,写的都是假的。原因是,之前在往子树递归的时候,需要去找子树的重心,这里会传入一个size函数,但是你直接传参数的话,原来dfs时的根是上一层的重心,不是这一层的重心,所以size是上一层时的size,而我们会发现,在当前这一层的所有子树内会有之多一个点是在上一层的时候位于当前这个点的父节点,而它在这时的正确size应该是这一层的总size减去x的size,不然的话再去找重心时用一个不对的size可能会找到的不是重心。
这个东西我也不知道只是常数上的优化还是可以被卡得复杂度不对,感觉上如果找重心时传进去的总size大了的话,找到的点应该会离这个子树的根比较近,所以应该是不优的。如果有哪位大佬知道这个东西到底是复杂度不对还是只是常数大,欢迎与我交流。

接下来说一下这个题的做法。
题目本身不是特别难,被许多大佬称为点分入门题、模板题。首先一个常见的点分治的问题是,如何避免来自同一个子树的路径在当前这一层产生的影响。我的写法是一个子树一个子树的做,先把这个子树的路径与之前已经考虑过的子树的路径合并来更新答案,之后再把这个子树内的路径加进去,这样就不会出现两条来自同一子树的路径在这一层更新了答案的情况了。这个题我们就是记录一下从子树到当前分治重心长度为k的路径最少经过几条路径,可以开一个大小为k的桶,注意每次更换分治重心的时候要把桶清空。但是清空的时候需要注意不能直接memset,因为桶的大小是1e6级别的,直接memset会超时,所以我们用与更新答案时相同的方法,dfs每一棵子树,然后把每一条路径的长度对于的最少边数改为inf。更新答案时的合并就是记录一个当前点离分治重心的距离和经过的边数,让用这个和已经有的桶里的答案来更新答案就好了。没有合法的就输出-1。

思路就是这个样子,具体实现可以看看代码。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,k,t[1000010],hed[200010],cnt,ans=1e9,vis[200010];
int sz[200010],mx[200010],rt;
struct node
{
    int to,next,dis;
}a[400010];
inline int read()
{
    int x=0;
    char s=getchar();
    while(s>'9'||s<'0')
    s=getchar();	
    while(s>='0'&&s<='9')
    {
        x=x*10+s-'0';
        s=getchar();
    }
    return x;
}
inline void add(int from,int to,int dis)
{
    a[++cnt].to=to;
    a[cnt].dis=dis;
    a[cnt].next=hed[from];
    hed[from]=cnt;
}
inline void getrt(int x,int fa,int size)
{
    sz[x]=1;
    mx[x]=0;
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(y==fa||vis[y])
        continue;
        getrt(y,x,size);
        sz[x]+=sz[y];
        mx[x]=max(mx[x],sz[y]);
    }
    mx[x]=max(mx[x],size-sz[x]);
    if(mx[x]<mx[rt])
    rt=x;
}
inline void dfs1(int x,int f,int dis,int shu)
{
    if(dis>k)
    return;
    ans=min(ans,t[k-dis]+shu);
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(y==f||vis[y])
        continue;
        dfs1(y,x,dis+a[i].dis,shu+1);
    }
} 
inline void dfs2(int x,int f,int dis,int shu)
{
    if(dis>k)
    return;
    t[dis]=min(t[dis],shu);
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(y==f||vis[y])
        continue;
        dfs2(y,x,dis+a[i].dis,shu+1);
    }
}
inline void dfs3(int x,int f,int dis)//清空 
{
    if(dis>k)
    return;
    t[dis]=1e9;
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(y==f||vis[y])
        continue;
        dfs3(y,x,dis+a[i].dis);
    }
}
inline void solve(int x,int size)
{
    vis[x]=1;
    t[0]=0;
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(vis[y])
        continue;
        dfs1(y,x,a[i].dis,1);
        dfs2(y,x,a[i].dis,1);
    }
    dfs3(x,0,0);
    for(int i=hed[x];i;i=a[i].next)
    {
        int y=a[i].to;
        if(vis[y])
        continue;
        rt=0;
        int ji;
//之前是随便找了一个点当根dfs,所以当前size不一定是以x为根时的size        
//这种情况其实只会有一个点的size不对,就是在之前当根时在x父节点的那个点
//那个点的size应该是总的size减去之前dfs时x的size 
        if(sz[y]<=sz[x]) 
        ji=sz[y];
        else
        ji=size-sz[x];
        getrt(y,0,ji);
        solve(rt,ji);
    }
}
int main()
{
    n=read();
    k=read();
    for(int i=1;i<=n-1;++i)
    {
        int x=read(),y=read(),z=read();
        ++x;
        ++y;
        add(x,y,z);
        add(y,x,z);
    }
    for(int i=0;i<=k;++i)
    t[i]=1e9;
    mx[0]=2e9;
    getrt(1,0,n);
    solve(rt,n);
    if(ans==1e9)
    printf("-1\n");
    else
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值