Explorer

https://ac.nowcoder.com/acm/contest/888/E
题意:每条边有一个 [ l , r ] [l,r] [l,r],表示通过的人的大小必须在此范围。求有多少种大小的人可以从1到达n。
思路:先对范围离散化,并以此为下标,建一棵线段树,结点维护某个范围区间有哪些边,通过并查集维护连通性。并查集要按秩合并,因为要保持形态。
注意不能直接维护闭区间,否则会有区间损失问题,同扫描线线段树https://blog.youkuaiyun.com/Wen_Yongqi/article/details/90181962
必须转成左闭右开区间,再维护。

综上所述:这种线段树每个结点u维护的是u到u+1范围的权值,可以理解为边,维护孤点会造成区间损失。扫描线r直接放进离散化,insert进线段树时r的位置-1,而这个线段树r+1放进离散化,insert进(r+1)的位置-1。这里-1是因为线段树的结点维护边,r+1是为了闭转开,这个问题扫描线是没有意义的(因为扫描线维护的比如[2,4]本来就是2-3和3-4,而这道题的[2,4]是2-3和3-4和4-5长度为3)。
补一个不+1的错误反例:

3 2
2 3 8 18
1 2 2 20
#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+100;
#define P pair<int,int>

int n,m,p[maxn],ans,d[maxn],sz;
vector<int> nums,s[maxn*4];
struct Edge{
    int u,v,l,r;
}e[maxn];
struct Tree{
    vector<P> v;
}tree[maxn*4];

int pos(int x){return lower_bound(nums.begin(),nums.end(),x)+1-nums.begin() ;}

void update(int o,int l,int r,int b,int c,int u,int v)
{
    if(b<=l&&r<=c)
    {
        tree[o].v.push_back(make_pair(u,v));
    }
    else 
    {
        int m=(l+r)/2;
        if(b<=m)update(o*2,l,m,b,c,u,v);
        if(c>m)update(o*2+1,m+1,r,b,c,u,v);
    }
}

int find(int x){return x==p[x]?x:find(p[x]);}

void Union(int o,int x,int y)
{
    x=find(x),y=find(y);
    if(x==y)return;
    if(d[x]>d[y])swap(x,y);
    p[x]=y;
    if(d[x]==d[y])d[y]++;
    s[o].push_back(x);
}

void solve(int o)
{
    for(int i=0;i<tree[o].v.size();i++)
    {
        int x=tree[o].v[i].first,y=tree[o].v[i].second; 
        Union(o,x,y);
    }
}

void del(int o)
{
    for(int i=0;i<s[o].size();i++)
    {
        int x=s[o][i];
        p[x]=x;
    }
}

void dfs(int o,int l,int r)
{
    solve(o);
    int x=find(1),y=find(n);
    if(x==y)
    {
        ans+=nums[r-1+1]-nums[l-1];//cout<<nums[l-1]<<" "<<nums[r-1]<<endl;
        del(o);
        return;
    }
    if(l==r)
    {
        del(o);
        return;
    }
    int m=(l+r)/2;
    dfs(o*2,l,m);
    dfs(o*2+1,m+1,r);
    del(o);
}

int main()
{
    //freopen("input.in","r",stdin);
    cin>>n>>m;
    for(int i=1;i<=m;i++)scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].r),nums.push_back(e[i].l),nums.push_back(e[i].r+1);
    for(int i=1;i<=n;i++)p[i]=i;
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    sz=nums.size(); 
    for(int i=1;i<=m;i++)update(1,1,sz,pos(e[i].l),pos(e[i].r+1)-1,e[i].u,e[i].v);
    dfs(1,1,sz);
    cout<<ans<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值