2019牛客暑期多校训练营(第八场)E:Explorer(线段树+按秩优化并查集)

【题解】

题意:给定一个无向图,有n个点m条边,每条边有四个属性u,v,l,r,表示连接u,v的这条边允许通过的路人的体形范围在[l,r],问从1走到n的可行体形有几个(2333333

思路:首先把区间离散化,然后建立一棵以区间为关键字的线段树,再把所有边都按区间加进对应的线段树节点。然后从线段树的根节点不断向下dfs,每次用按秩(即深度)合并优化并查集,不能用路径压缩,因为回溯的时候要撤销并查集操作。到了叶子结点的时候查询下1和n是否在一个集合里,是的话就加上这个区间的贡献,不是的话就继续向下dfs。

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
const int N=1e5+10;
struct edge{ //边
    int u,v,l,r;
}f[N];
int pre[N],rk[N]; //pre[]表示父节点,rk[]表示并查集的秩
int q[N<<1];
vector <P> a[N<<3]; //线段树
vector <int> t[30];
P V;
int n,m,ans;
void build(int x,int l,int r,int fl,int fr)
{
    if(l==fl&&r==fr){
        a[x].push_back(V);
        return;
    }
    int mid=(l+r)>>1;
    if(fr<=mid) build(x<<1,l,mid,fl,fr);
    else if(fl>=mid) build(x<<1|1,mid,r,fl,fr);
    else build(x<<1,l,mid,fl,mid),build(x<<1|1,mid,r,mid,fr);
}
int Find(int x)
{
    return pre[x]==x?x:Find(pre[x]);
}
void join(int rt,int x,int y,int dep)
{
    x=Find(x),y=Find(y);
    if(x!=y){
        if(rk[x]<=rk[y]){ //按秩合并
            pre[x]=y,t[dep].push_back(x);
            if(rk[x]==rk[y]) rk[y]++;
        }
        else{
            pre[y]=x,t[dep].push_back(y);
        }
    }
}
void dfs(int x,int l,int r,int dep) //x表示节点编号,dep表示节点深度
{
    t[dep].clear();
    for(auto i:a[x]) join(x,i.first,i.second,dep);
    if(l==r-1){
        if(Find(1)==Find(n)) ans+=q[r]-q[l]; //更新贡献
        for(auto i:t[dep]) pre[i]=i;
        return;
    }
    int mid=l+r>>1;
    dfs(x<<1,l,mid,dep+1);
    dfs(x<<1|1,mid,r,dep+1);
    for(auto i:t[dep]) pre[i]=i;
}
int main()
{
    scanf("%d%d",&n,&m);
    int cnt=0;
    for(int i=1;i<=n;i++) pre[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&f[i].u,&f[i].v,&f[i].l,&f[i].r);
        q[++cnt]=f[i].l; q[++cnt]=f[i].r+1;
    }
    sort(q+1,q+cnt+1); //把区间排序进行离散化
    cnt=unique(q+1,q+cnt+1)-q-1;
    for(int i=1;i<=m;i++){
        V=P(f[i].u,f[i].v);
        int p1=lower_bound(q+1,q+cnt+1,f[i].l)-q;
        int p2=lower_bound(q+1,q+cnt+1,f[i].r+1)-q;
        build(1,1,cnt,p1,p2); //以区间为关键字建树
    }
    ans=0;
    dfs(1,1,cnt,1); //从根1向下dfs
    printf("%d\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值