「2019牛客多校第八场E」Explorer【离散化+线段树分治 或 LCT】

Explorer

题面

在这里插入图片描述

题意

  • 就是给你一张无向图,如果需要通过该条边,则 s i z e size size值的取值区间为 [ l , r ] [l,r] [l,r],问你从 1 1 1走到 n n n有多少种可行的 s i z e size size.

题解

  • 线段树分治(时间分治)模板题
  • 线段树分治考虑的是统计每一个操作可以影响的时间区间,然后用线段树去维护这些影响的区间,最后做一次 d f s dfs dfs统计所有询问的答案,对于这题,显然想到的是考虑每一条边的添加所产生的 s i z e size size区间贡献,也就是说,应该将 s i z e size size区间建一颗线段树,当然需要离散化一下,然后跑线段树分治,到达某个叶子结点时,如果可撤销并查集中 1 1 1 n n n在同一个几何中,则当前节点对应的区间加到答案中
  • 另外一个做法是 L C T LCT LCT,和BZOJ3669的做法很相似,这题也是每条边相当于有两个属性 l l l r r r,即将所有边按照 r r r降序排序,然后去维护 l l l的最小生成树,然后查询的时候就是查询路径上最大边权作为可行答案的左区间,右区间即为当前枚举的区间右端点

线段树分治代码

#include<bits/stdc++.h>

using namespace std;
const int maxn=2e5+10;
const int maxm=2e5+10;
int n,m,le[maxn],ri[maxn],b[maxn],cnt;

struct operate{
    int x,y,l,r;
    operate(int a=0,int b=0,int c=0,int d=0) {
        x=a;y=b;l=c;r=d;
    }
}o[maxm],sta[maxm];

namespace dsu{
    int fa[maxn],dep[maxn],tot;  //tot表示栈sta里的元素个数
    void init(int k) {
        tot=0;
        for(int i=1;i<=k;i++) fa[i]=i,dep[i]=0;
    }
    int find(int x) {
        return fa[x]==x?x:find(fa[x]);
    }
    void merge(int x,int y) {
        x=find(x),y=find(y);
        if(x==y) return;
        if(dep[x]>dep[y]) swap(x,y);
        if(dep[x]==dep[y]) dep[y]++;
        sta[++tot]=operate(x,y,0,0);  //注意这个入栈的构造函数
        fa[x]=y;
    }

    void undo(int id) {
        while(tot>id) {
            int x=sta[tot].x,y=sta[tot--].y;
            if(dep[y]==dep[x]+1) dep[y]--;
            fa[x]=x;
        }
    }
};
using namespace dsu;

namespace segment_tree{
    vector<operate> add[maxm<<2];
    void update(int id,int L,int R,int l,int r,int k) {
        if(le[L]>=l&&ri[R]<=r) {add[id].push_back(o[k]);return;}
        int mid=(L+R)>>1;
        if(l<=ri[mid]) update(id<<1,L,mid,l,r,k);
        if(r>ri[mid]) update(id<<1|1,mid+1,R,l,r,k);
    }
    int work(int id,int L,int R) {
        int now=tot,ans=0;
        for(int i=0;i<add[id].size();i++) merge(add[id][i].x,add[id][i].y);
        if(L==R) {
            if(find(1)==find(n)) ans+=(ri[R]-le[L]+1);
        }else{ 
            int mid=(L+R)>>1;
            ans+=work(id<<1,L,mid);ans+=work(id<<1|1,mid+1,R);  
        }
        undo(now);  //叶子结点也要undo
        return ans;
    }
};
using namespace segment_tree;

int main()
{
    scanf("%d %d",&n,&m);
    init(n);
    for(int i=1;i<=m;i++) {
        scanf("%d %d %d %d",&o[i].x,&o[i].y,&o[i].l,&o[i].r);
        b[++cnt]=o[i].l;b[++cnt]=o[i].r+1;
    }
    sort(b+1,b+cnt+1);
    int num=unique(b+1,b+cnt+1)-b-1;
    for(int i=1;i<num;i++) le[i]=b[i],ri[i]=b[i+1]-1; 
    for(int i=1;i<=m;i++) {
        update(1,1,num-1,o[i].l,o[i].r,i);
    }
    printf("%d\n",work(1,1,num-1));
}

L C T LCT LCT代码

#include<bits/stdc++.h>
 
using namespace std;
const int maxn=2e5+10;
#define inf 0x3f3f3f3f
 
 
/*
1. 实链的根结点不记录father,但是他的father记录他为儿子
2. 虚链的根结点记录father,但是他的father不记录他为儿子
3. LCT维护树链上的值的时候得先把节点value加上去,然后才能link,因为如果link之后改value,有可能在link的时候一些节点信息没有更新为正确值
4.find_root很慢,可以用并查集辅助查询
*/
 
 
struct node{
    int x,y,l,r;
    node(int a=0,int b=0,int c=0,int d=0) {
        x=a;y=b;l=c;r=d;
    }
    friend bool operator<(const node&a,const node&b) {
        if(a.r==b.r) return a.l>b.l;
        return a.r>b.r;
    }
}o[maxn];
 
namespace LCT{
    int ch[maxn][2],fa[maxn],sta[maxn],mark[maxn];
    long long val[maxn],ans[maxn],sum[maxn];
    inline bool not_root(int x) {
        return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
    }
    inline int dir(int x) {
        return ch[fa[x]][1]==x;
    }
    inline void add_mark(int x) {  //将x这颗子树翻转
        swap(ch[x][0],ch[x][1]);
        mark[x]^=1;
    }
 
    inline void push_down(int x) {
        if(mark[x]) {
            if(ch[x][0]) add_mark(ch[x][0]);
            if(ch[x][1]) add_mark(ch[x][1]);
            mark[x]=0;
        }
    }
 
    inline void push_up(int x) {
        ans[x]=x;
        if(val[ans[ch[x][0]]]>val[ans[x]]) ans[x]=ans[ch[x][0]];
        if(val[ans[ch[x][1]]]>val[ans[x]]) ans[x]=ans[ch[x][1]];   
    }
 
    inline void pushall(int x) {
        if(not_root(x)) pushall(fa[x]);
        push_down(x);
    }
 
    inline void rotate(int x){
        int y=fa[x],z=fa[y],k=dir(x);
        if(ch[x][k^1]) fa[ch[x][k^1]]=y;ch[y][k]=ch[x][k^1];
        if(not_root(y)) ch[z][dir(y)]=x;fa[x]=z;
        ch[x][k^1]=y;fa[y]=x;
        push_up(y);push_up(x);
    }
    inline void splay(int x,int goal=0) {
        pushall(x);
        while(not_root(x)) {
            int y=fa[x],z=fa[y];
            if(not_root(y)) {
                if(dir(x)==dir(y)) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        //push_up(x);
    }
 
    inline void access(int x) {    //从原树的根向x拉一条实链
        for(int y=0;x;y=x,x=fa[x]) {
            splay(x);ch[x][1]=y;push_up(x);
        }
    }
 
    inline void make_root(int x) {  //使x成为splay的根
        access(x);splay(x);add_mark(x);
    }
 
    inline int find_root(int x) {   //找到x在原树中的根
        access(x);splay(x);
        while(ch[x][0]) push_down(x),x=ch[x][0];
        splay(x);
        return x;
    }
 
    inline void split(int x,int y) {   //拉出一条x->y的实链,y为splay根
        make_root(x);access(y);splay(y);
    }
 
    inline bool link(int x,int y) {    //连接x与y,若已经在同一颗原树中,返回0
        make_root(x);
        if(find_root(y)==x) return 0;
        fa[x]=y;return 1;
    }
    inline bool cut(int x,int y) {
        make_root(x);
        if(find_root(y)!=x||fa[y]!=x||ch[y][0]) return 0;
        fa[y]=ch[x][1]=0;
        push_up(x);
        return 1;
    }
    inline long long query_max(int l,int r) {
        split(l,r);
        return ans[r];
    }
    inline long long query_sum(int l,int r) {
        split(l,r);
        return sum[r];
    }
};
using namespace LCT;
 
vector<pair<int,int> >res;
int n,m;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d %d %d %d",&o[i].x,&o[i].y,&o[i].l,&o[i].r);
    sort(o+1,o+m+1);
    for(int i=1;i<=m;i++) {
        if(find_root(o[i].x)==find_root(o[i].y)) {
            int x=query_max(o[i].x,o[i].y);
            if(o[x-n].l>o[i].l) {
                cut(x,o[x-n].x),cut(x,o[x-n].y);
                val[n+i]=o[i].l;link(n+i,o[i].x),link(n+i,o[i].y);
            }
        }else {
            val[n+i]=o[i].l;link(n+i,o[i].x);link(n+i,o[i].y);
        }
 
        if(find_root(1)==find_root(n)) {
            int x=query_max(1,n);
            if(o[x-n].l<=o[i].r) res.push_back(make_pair(o[x-n].l,o[i].r));
        }
    }
    sort(res.begin(),res.end());
    int tot=res.size();long long ans=0;
    for(int i=0;i<tot;) {
        int j=i,maxx=res[i].second;
        while(j+1<tot&&res[j+1].first<=maxx) {
            maxx=max(maxx,res[j+1].second);
            j++;
        }
        ans+=1LL*(maxx-res[i].first+1);
        i=j+1;
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值