牛客多校第八场 E - Explorer(时间分治 + 离散化 + 并查集 + 线段树)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
巧妙的时间分治思想
根据通道的大小的大小建立一棵线段树。
对于每条边,根据它 l 和 r的大小把它放到线段树对应的位置。
然后从根开始dfs,每经过一个结点,就把结点存的边都加进来,通过并查集维护,跑到叶子结点的时候,判断一下1和n在不在同一个并查集中,在的话表明叶子节点所代表的大小可以使得主角从1抵达n,把它记录下来。合并并查集的时候按秩合并
观察题目给的数据范围可知n和m都在1e5里面,所以可以把边的 l 和 r 的范围离散到1e5左右。

注意把 边的大小 离散化到线段树上时开闭区间问题,代码里是 点 对应 点,是两端都是闭

//建立的线段树,叶子节点的值对应 离散化前 通道大小的端点的值
//比如离散化前,通道大小为17 19,离散化后为 3 4,则3 对应 17,4对应19,两端都是闭 
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5 + 10;

struct node{
	int u,v,l,r,ml,mr;
}edge[MAXN];

struct node1{
	int value,num,v;
}value[MAXN<<2];

bool cmp(node1 x,node1 y){return x.value < y.value;}

bool cmp1(node1 x,node1 y){return x.num<y.num;}

struct node3{
	node3(int a = 0,int b = 0):u(a),v(b){}
	int u,v;
};

struct node2{
	int l,r;
	vector<node3> q;
	int mid(){return (r - l) / 2 + l;} 
}tree[MAXN<<4];

void build(int st,int ed,int x)
{
	tree[x].l = st;
	tree[x].r = ed;
	if (st == ed) return;
	build(st,tree[x].mid(),x * 2);
	build(tree[x].mid()+1,ed,x * 2 + 1);
}
	
void modify(int st,int ed,int x,node3 t)
{
	if (tree[x].l >= st && tree[x].r <= ed) 
	{
		tree[x].q.push_back(t);
		return;
	}
	int mid = tree[x].mid();
	if (mid >= st) modify(st,ed,x * 2,t);
	if (ed > mid) modify(st,ed,x*2+1,t);
}

int f[MAXN],n,m,len = 1,size[MAXN];

node3 ans[MAXN];

int find(int x)
{
	while (x != f[x]) x = f[x];
	return x;
}

struct save{
	save(int c = 0,int d = 0,int e = 0,int f = 0):fx(c),fy(d),sizefx(e),sizefy(f){} 
	int fx,fy,sizefx,sizefy;
};

void dfs(int u)
{
	vector<save> s;
	for (int i = 0;i < tree[u].q.size();i++)//枚举当前结点存的边 
	{
		int x = tree[u].q[i].u,y = tree[u].q[i].v;
		int fx = find(x),fy = find(y);
		s.push_back(save(fx,fy,size[fx],size[fy]));//记录下来,用于回退 
		if (fx == fy) continue;
		if (size[fx] < size[fy]) f[fx] = fy;//并查集按秩合并 
		else
		{
			f[fy] = fx;
			if (size[fx] == size[fy]) size[fx]++;
		}
	}
	if (tree[u].l == tree[u].r)//如果到了叶子节点 
	{
		int f1 = find(1),fn = find(n);
		if (f1 == fn)
		{
			if (ans[len].u == 0) ans[len].v = ans[len].u = tree[u].l;//记录答案左端点 
			else ans[len].v = tree[u].l;//记录答案右端点 
		}
		else if (ans[len].v != 0) len++;
	}
	if (tree[u].l != tree[u].r)
	{
		dfs(u * 2);
		dfs(u * 2 + 1);
	}
	for (int i = s.size() - 1;i>=0;i--)//回退 
	{
		save t = s[i];
		int fx = t.fx,fy = t.fy;
		size[fx] = t.sizefx,size[fy] = t.sizefy,f[fx] = fx,f[fy] = fy;
	}
}

int main() 
{
	scanf("%d%d",&n,&m);
	for (int i = 1;i<=n;i++) f[i] = i,size[i] = 1;//并查集初始化,size表示并查集的秩 
	int lenv = 1;//value用来离散化 
	for (int i = 1;i<=m;i++)
	{
		scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].l,&edge[i].r);
		value[value[lenv].num = lenv].value = edge[i].l - 1; lenv++;
		value[value[lenv].num = lenv].value = edge[i].l; lenv++;
		value[value[lenv].num = lenv].value = edge[i].r; lenv++;
		value[value[lenv].num = lenv].value = edge[i].r + 1; lenv++;
		//防止相邻的两个区间都可以从1到n,而两个区间之间的值不可以时,把两个区间直接合并起来 
	}
	//离散化 
	lenv--;
	sort(value+1,value+lenv+1,cmp);
	int count = 0;
	for (int i = 1;i<=lenv;i++)
	{
		if (value[i].value != value[i - 1].value) value[i].v = ++count;
		else value[i].v = count;
	}
	sort(value+1,value+lenv+1,cmp1);
	for (int i = 1;i<=lenv;i++)
	{
		if (i % 4 == 2)			edge[i / 4 + 1].ml = value[i].v;
		else if (i % 4 == 3)	edge[i / 4 + 1].mr = value[i].v;
	}
	build(1,count,1);
	for (int i = 1;i<=m;i++) modify(edge[i].ml,edge[i].mr,1,node3(edge[i].u,edge[i].v));//把边添加到线段树中 
	dfs(1);//从根节点开始跑dfs枚举大小寻找答案 
	len--;
	int sum = 0;
	sort(value+1,value+lenv+1,cmp);
	for (int i = 1,j = 1,last;i<=lenv && j<=len;i++)//把线段树上的大小映射回原来得到大小计算答案 
	{
		if (value[i].v == ans[j].u) last = value[i].value;
		if (value[i].v == ans[j].v) sum += value[i].value - last + 1,j++;
	}
	cout<<sum;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值