2019牛客暑期多校8E:Explorer【线段树分治+可撤销并查集】

题目:

2019牛客暑假多校8E:Explorer

题意:

给定一张图,每条边有个区间【L,R】,当你的权值在这个区间时才能走这条边,问一共有多少权值可以从 1 走到 N

笔记:

可撤销并查集:

就是没有路径压缩的并查集,但为了快速找到祖宗节点,采用启发式合并的方法,每次将节点数量少的集合合并到节点多的集合上;这样合并后,从叶子节点每往上移动一个节点,总的节点数至少增加一倍,所以整颗树的高度是 logN ,那么每次找祖宗节点的复杂度也就是O(logN),这样做的好处就是方便撤销

分析:

这道题的做法就是经典的时间分治,按照L,R的值建一颗线段树,把每条边存到对应【L,R】的区间的节点上,然后遍历一遍线段树,同时将每个节点保存的边加入并查集,每次只需要判断 1,N 是否在一个集合就能判断这段区间对应的【xL,xR】是否合法,回溯的时候撤销加入的边;这道题的 L,R比较大,考虑将 L,R+1离散化后线段树的叶子节点表示一段连续的数字即可

代码:

#include <bits/stdc++.h>

#define f first
#define s second
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int maxn = 1e5+25;
int n,m,c[maxn<<1],cnt;
struct node{
    int u,v,l,r;
}q[maxn];
inline int getid(int x){
    return lower_bound(c+1,c+cnt,x)-c;
}
std::vector<int> v[maxn<<3];
void updata(int l,int r,int L,int R,int x,int t){
    if(l > R || r < L) return ;
    if(l >= L && r <= R){
        v[x].push_back(t);
        return ;
    }
    int mid = (l+r) >> 1;
    updata(l,mid,L,R,x<<1,t);
    updata(mid+1,r,L,R,x<<1|1,t);
}
int fa[maxn],sz[maxn];
int Find(int x){
    return fa[x]==x ? x : Find(fa[x]);
}
stack<pii> s;
inline void mix(int x,int id){
    int fu = Find(q[x].u);
    int fv = Find(q[x].v);
    if(fu != fv){
        if(sz[fu] > sz[fv]){
            fa[fv] = fu; sz[fu] += sz[fv];
            s.push(pii(id,fv));
        }
        else{
            fa[fu] = fv; sz[fv] += sz[fu];
            s.push(pii(id,fu));
        }
    }
}
inline void cancal(int id){
    while(!s.empty()&&s.top().f==id){
        int x = s.top().s; s.pop();
        sz[fa[x]] -= sz[x];
        fa[x] = x;
    }
}
void query(int l,int r,int x,LL &ans){
    for(int i = 0;i<(int)v[x].size();++i) mix(v[x][i],x);
    if(Find(1) == Find(n)){
        ans += c[r+1]-c[l];
        cancal(x);
        return ;
    }
    if(l == r){
        cancal(x);
        return ;
    }
    int mid = (l+r) >> 1;
    query(l,mid,x<<1,ans);
    query(mid+1,r,x<<1|1,ans);
    cancal(x);
}
int main(){
    scanf("%d %d",&n,&m); cnt = 1;
    for(int i = 0;i <= n; ++i) fa[i] = i,sz[i] = 1;
    for(int i = 0;i < m; ++i){
        scanf("%d %d %d %d",&q[i].u,&q[i].v,&q[i].l,&q[i].r);
        c[cnt++] = q[i].l, c[cnt++] = q[i].r+1;
    }
    sort(c+1,c+cnt); cnt = unique(c+1,c+cnt)-c;
    for(int i = 0;i < m; ++i){
        int L = getid(q[i].l);
        int R = getid(q[i].r+1)-1;
        updata(1,cnt-2,L,R,1,i);
    }
    LL ans = 0;
    query(1,cnt-2,1,ans);
    cout << ans << '\n';
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值