洛谷4185

题意简述

给一个带权值的树(点数<=1e5,边权<=1e9),Q(Q<=1e5)个询问,每次有一个起点 v i v_i vi和长度 k i k_i ki(1<=vi<=n,ki<=1e9),求v点到多少点的路径上最短的那个边权(树上路径是唯一的,所以这个值是确定的)>=k。

数据

输入:
4 3
1 2 3
2 3 2
2 4 4
1 2
4 1
3 1
输出:
3
0
2

样例解释

图片

2开始到13,到32,到44,3>=11开始到32,到43,到230>=42>=3

思路

暴力模拟
显然不行
UPD on 2019.4.1(愚人节)
(今天给学妹学弟做这个题,给他按原来的讲,发现没有讲懂。我们写博客的要站在不会这个题的读者角度上多想想,所以我决定改一下写法)。

但是我们保留这个想法,想想如何优化这个暴力。
暴力:

对于询问中的点 v v v,我们需要枚举一个终点 j ( j ! = v ) j(j !=v ) j(j!=v),判断 v v v j j j的最短的边是否 &gt; = k &gt;=k >=k,是则统计答案

但是我们仔细想想,发现统计路径很慢,代码也很长(要写LCA),wdnmd这tm是暴力!!!
那么,如何快速(时间少+代码短)的统计i到多少点距离>=k呢?
并查集。

把读入的边离线存下来,对于一个 v v v,先初始化并查集,枚举每一条边,如果这个边的长度>=k,就在并查集上合并这个边连的两个点(代码中是E[i].u和E[i].v)。最后统计点v所在的集合大小-1(为什么-1?因为v自己不能算)

代码短了,但是复杂度依然是O(很大)(实际上是 n × q n\times q n×q)。
那么,我们如何继续优化这个思路呢?
我们发现,其实不用每次都初始化。设想这样一个场景:如果我们把询问也离线,按某种玄学的东西排一下序,然后我们每次只需要在原来的基础上新加边,而不是每次都初始化然后重新加,那就珂以优化到 O ( n l o g n ) O(nlogn) O(nlogn)(如果要排序的话)了。
如何实现这个设想呢?
把边按w(边长)降序排序,把询问按k降序排序,就珂以实现了。

证明(不太严谨,凑合看看):
设询问是Q,有2个成员变量是k和v
对于询问Q[i],如果有一个边的长度>=Q[i-1].k,又因为我们按降序排序,所以一定满足Q[i-1].k>=Q[i].k,根据不等式的传递性我们知道:该边长>=Q[i].k,所以我们就不用重复算了,只要算新加的即可。

这样下来我们的复杂度就是 O ( n l o g n + q l o g q + n ) O(nlogn+qlogq+n) O(nlogn+qlogq+n)
(排序是nlogn+qlogq,处理询问是n)
但是我们要按输入顺序回答询问,所以我们只需要再加两个成员变量id和ans,id保存这是第几个询问,ans表示该询问的答案。最后按id排一下序,顺序输出ans即可。

代码:

#include<bits/stdc++.h>
#define N 1001000
using namespace std;
class DSU
{
    private:
        int Father[N];
    public:
        int Cnt[N];
        void Init()
        {
            for(int i=0;i<N;i++)
            {
                Father[i]=i;
                Cnt[i]=1;
            }
        }
        int Find(int x)
        {
            return x==Father[x]?x:Father[x]=Find(Father[x]);
        }
        void Merge(int x,int y)
        {
            int ax=Find(x),ay=Find(y);
            if (Cnt[ax]<Cnt[ay])
            {
                Cnt[ay]+=Cnt[ax];
                Father[ax]=ay;
            }
            else
            {
                Cnt[ax]+=Cnt[ay];
                Father[ay]=ax;
            }
        }
}D;

struct tmp1
{
    int u,v,w;
    const bool operator<(const tmp1 CompWith) const
    {
        return w>CompWith.w;
    }
}E[N];
struct tmp2
{
    int k,v,id,ans;
    const bool operator<(const tmp2 CompWith) const
    {
        return k>CompWith.k;
    }
}Q[N];bool cmp(tmp2 x,tmp2 y) {return x.id<y.id;}
//询问结构体要写两个比较函数,一个按k降序,一个按id升序
int n,q;

void Input()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        if (u>v) u^=v^=u^=v;
        scanf("%d%d%d",&u,&v,&w);
        E[i]=(tmp1){u,v,w};//离线存储边
    }
    for(int i=1;i<=q;i++)
    {
        int k,v;
        scanf("%d%d",&k,&v);
        Q[i]=(tmp2){k,v,i,0};//离线存储询问
    }
}

void Solve()
{
    sort(E+1,E+n);
    sort(Q+1,Q+q+1);//排序

    int j=1;//表示我们当前用到了哪条边,每次只要考虑j后面即可
    for(int i=1;i<=q;i++)
    {
        while(E[j].w>=Q[i].k)//重复加边
        {
            int u=E[j].u,v=E[j].v;
            if (D.Find(u)!=D.Find(v)) D.Merge(u,v);
            j++;
        }
        Q[i].ans=D.Cnt[D.Find(Q[i].v)]-1;//还记得为什么-1么(不记得的emmm。。。)
    }
    sort(Q+1,Q+q+1,cmp);
    for(int i=1;i<=q;i++)
    {
        printf("%d\n",Q[i].ans);//按输入顺序回答询问
    }
}

main()
{
    D.Init();
    Input();
    Solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值