cdq(时间分治)

Explorer

题意:给定n个点m条无向边,每条边有一个宽度(L,R)。现在有一个人在一号节点,这个人的宽度不知,问最后这个人 到达n号节点可以有多少种大小。通过一条边的条件是这个人的宽度大小在这条边的范围之内。(用词不准确,但大概题意就是这样TTTT。。。)。

菜鸟一看见这道题,这不dfs搜索一下就ok了,菜鸟还是头脑太简单了。
看了官方题解,时间分治????这是什鬼东西呢,表示不知道。后来才知道就是cdq分治,以前听说过cdq,但不知道还有一个名字时间分治。



正文开始

前置技能:并查集按秩合并
   并查集集合并的时候可以通过路径压缩的方式去提高查询的效率,缺点就是如果想撤销操作的话就比较困难了。因此有了另外一种合并方式——按秩合并。这里的秩指的是树的高度,有的地方也说是集合的大小。
  每次把秩小的合并到秩大的。
为什么要这样呢?我们没有路劲压缩,但希望可以提高查询的效率,因此需要尽量维护这颗树的平衡

H[b] = max(H[a] + 1, H[b]);

H[]:表示节点秩的大小。并且b的秩大于等于b的秩。如果b的秩严格大于a的秩,那么合并之后的秩不变。如果a,b的秩相同。那么就需要加一。想不明白的自己画个图看一看就很清楚了。

此题思路:
  这里的宽度区间比较大,所以需要先离散化一下。对于一条可以从1到n的路径,其实我们并不关心这条路径具体走过那些节点。我们只需要知道它可以到达n,以及这条路劲上的区间并。假设对于选择了k条边,那么把每条边对应的两个点用并查集合并到一起,如果最后1和n在同一个集合里。那是不是就说明这条路劲可行呢?显然是的。
具体怎么做呢。建立一颗线段树,每个节点表示这个区间内的边的节点。假设一条边是(u,v,L,R)那么就需要在区间[L,R]上加入a,b这两个点,因为对于所有[L,R]的子区间,他们都是可以走a,b这条边的。把所有边的信息加入线段树之后。就可以开始查询了。
从根节点开始往下寻找。每经过一个节点就把这个区间内的点用并查集合并。最后走到叶子节点的时候判断一下1和n是否联通,如果联通就累加上该区间对答案的贡献。最后回溯的时候需要撤销并查集的操作,因此合并并查集的时候记录一下合并的点的信息,方便后来撤销操作。

小提示:

在区间离散化的时候,右区间R+1,可以防止左右端点相同,区间退化为点的情况。最后的右区间对应的离散化后的小标需要减1.

AC_CODE

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int>PII;
const int N = 100 * 1000 + 10;
int n, m, sz;
int H[N * 2], F[N * 2], a[N * 2];
int ans;
struct node {
	int a, b, l, r;
}edge[N];
void init() {
	for (int i = 1; i <= n; i++) {
		H[i] = 1; F[i] = i;
	}
}
int Find(int x) { return F[x] == x ? x : Find(F[x]); }
int getid(int x) { return lower_bound(a + 1, a + 1 + sz, x) - a; }

vector<PII>sum[N << 3];
void add(int rt, int l, int r, int L, int R, int a, int b) {
	if (l >= L && r <= R) {
		sum[rt].push_back({ a,b }); return;
	}
	int mid = l + r >> 1;
	if (L <= mid)add(rt << 1, l, mid, L, R, a, b);
	if (R > mid)add(rt << 1 | 1, mid + 1, r, L, R, a, b);
}

void ask(int rt, int l, int r) {
	vector<node>re;
	for (int i = 0; i < sum[rt].size(); i++) {
		int a = sum[rt][i].first, b = sum[rt][i].second;
		a = Find(a); b = Find(b);
		if (a == b)continue;
		if (H[a] > H[b])swap(a, b);
		re.push_back(node{ a,b,H[b],0 });
		F[a] = b;
		H[b] = max(H[a] + 1, H[b]);
	}
	if (l == r) ans += (Find(1) == Find(n)?a[l+1]-a[l]:0);
	else {
		int mid = l + r >> 1;
		ask(rt << 1, l, mid);
		ask(rt << 1 | 1, mid + 1, r);
	}
	for (int i = 0; i<re.size(); i++) {
		F[re[i].a] = re[i].a;
		H[re[i].b] = re[i].l;
	}
}

int main() {
	scanf("%d%d", &n, &m);
	init();
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d%d", &edge[i].a, &edge[i].b, &edge[i].l, &edge[i].r);
		edge[i].r++;
		a[i] = edge[i].l; a[i + m] = edge[i].r;
	}
	sort(a + 1, a + 1 + 2 * m);
	sz = unique(a + 1, a + 1 + 2 * m) - a - 1;

	for (int i = 1; i <= m; i++) {
		add(1, 1, sz, getid(edge[i].l), getid(edge[i].r)-1, edge[i].a, edge[i].b);
	}

	ask(1, 1, sz);

	printf("%d\n", ans);

	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值