(Nowcoder) E.Explorer (线段树+可撤销并查集)

16 篇文章 0 订阅
10 篇文章 0 订阅

传送门

题意:给出一个无向图,每条边有一个size区间[l,r],代表可以通过人的大小,求可以从1到n,人的大小的方案数。

解:可以lct做,可是我还没学过lct。线段树+并查集的做法,我们先离散化size,使得线段树的节点代表size的一段区间,全部左开右闭(这样好处理)。然后我们将符合节点的边加进去,不需要下放。最后我们一遍深搜,深搜的同时将该节点的边,用并查集维护点之间的联通性,搜到叶子节点的时候,说明涵盖了这段区间的边都已经被加进去了,判断1和n的联通性,如果联通,那代表这段size的区间内的值都是可以使得1到n的,加上贡献。回溯的时候,我们将加的边撤销即可。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define ms(_data,v) memset(_data,v,sizeof(_data))
#define SZ(a) int((a).size())
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int maxn=1e5+5;
//il int Add(int x,int y) {return x+y>=mod?x+y-mod:x+y;}
//il int Mul(ll x,int y) {return x*y>=mod?x*y%mod:x*y;}
int s[maxn<<2],n,m,ans=0;
vector<int> un[maxn<<3][2];
struct Collect{
	int fa[maxn],rk[maxn];
	void init(){
		for(int i=0;i<maxn;++i) fa[i]=i,rk[i]=0;
	}
	int search(int x){
		while(x^fa[x]) x=fa[x];
		return x; 
	}
	void unite(int x,int y,int rt){
		x=search(x),y=search(y);
		if(x==y) return ;
		if(rk[x]>rk[y]){
			fa[y]=x;
			un[rt][1].pb(x),un[rt][0].pb(y); //father and son 
		}
		else{
			fa[x]=y;
			if(rk[x]==rk[y]) rk[y]++;
			un[rt][1].pb(y),un[rt][0].pb(x);
		}
	}
	void reunite(int rt){
		int len=SZ(un[rt][1]);
		for(int i=0;i<len;++i){
			rk[un[rt][1][i]]-=rk[un[rt][0][i]];
			fa[un[rt][0][i]]=un[rt][0][i];
		}
		un[rt][1].clear(),un[rt][0].clear();
	}
}co;
struct Tree {
	struct node {
		int l,r;
		bool fg;
		vector<int> v;
	} sum[maxn<<3];
	void build(int l,int r,int rt) {
		if(l==r) {
			sum[rt].fg=1;
			sum[rt].l=s[l],sum[rt].r=s[l+1];
			return ;
		}
		int mid=(l+r)>>1;
		build(l,mid,rt<<1);
		build(mid+1,r,rt<<1|1);
		sum[rt].l=sum[rt<<1].l,sum[rt].r=sum[rt<<1|1].r;
	}
	void update(int L,int R,int rt,int u,int v) {
		if(L<=sum[rt].l && R>=sum[rt].r){
			sum[rt].v.pb(u);
			sum[rt].v.pb(v);
			return ;
		}
		if(L<sum[rt<<1].r) update(L,R,rt<<1,u,v);
		if(R>sum[rt<<1|1].l) update(L,R,rt<<1|1,u,v);
	}
	void over(int rt){
		if(SZ(sum[rt].v)){
			int len=SZ(sum[rt].v);
			for(int i=0;i<len;i+=2){
				co.unite(sum[rt].v[i],sum[rt].v[i+1],rt);
			}
		}
		if(sum[rt].fg){
			if(co.search(1)==co.search(n)){
				ans+=sum[rt].r-sum[rt].l;
			}
			return ;
		}
		over(rt<<1);
		co.reunite(rt<<1);
		over(rt<<1|1);
		co.reunite(rt<<1|1);
	}
}tree;
int U[maxn],V[maxn],L[maxn],R[maxn];
int main() {
	std::ios::sync_with_stdio(0);
	scanf("%d%d",&n,&m);
	int tot=0;
	for(int i=1; i<=m; ++i) {
		scanf("%d%d%d%d",&U[i],&V[i],&L[i],&R[i]);
		s[++tot]=L[i],s[++tot]=(++R[i]);
	}
	sort(s+1,s+tot+1);
	tot=unique(s+1,s+tot+1)-(s+1);
	tree.build(1,tot-1,1);
	co.init();
	for(int i=1;i<=m;++i){
		tree.update(L[i],R[i],1,U[i],V[i]);
	}
	tree.over(1);
	printf("%d\n",ans);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值