AcWing 164. 可达性统计

知识点:拓扑排序,位运算,bitset

这个题才刚刚写了,虽然是拓扑排序的例题,但是其实就是对位运算和bitset的练习题,做了这么多的题,才第一次明确遇见集合的概念

至于为什么是集合,我觉得最基本的原因是这个题是一个图,所以可以用集合(虽然还没学过离散数学,尤其是集合论与图论),对每个点,我们都用一个集合来表示这个点能到达的点,很显然拓扑排序最后的点只能到自己,倒数第二个点能到自己或者最后一个点,如果有边的话,那么这个题我们可以按照拓扑排序的逆序,从后向前推,每到一个点,求自身,与所有出边相连的点的集合求并集,那么就是当前点的可以到达的点的集合,最后输出每个点的集合的大小就行了,

然后是关键,怎么来表示集合,可以用位运算,比如说用二进制的1101,来表示集合里面有1,3,4,但是没有2,求集合的并,就用二进制的并运算就行了,但是还有一个问题,这个题的点的个数是3e4,不像别的搜索的题目,很少,10个20个,就用一个int就行了,这里显然不行,我们就按一个int表示30位,那么一个点需要1000个int,我一开始就是这样子写的,但是最后一个点超时了,然后看了看发现bitset是真的强,上面用了1000个int来表示30000个数,bitset用一个就行了,而且修改,求并,查询里面1的个数等等操作,真的是很方便,这个stl真的很强感觉

然后就是对于状态压缩的理解,之前做过两个BFS题,那个是把10维的数组给压缩成1维了,这里其实是和数独问题一样,把可供选择的选项,或者是集合里面的元素,若干个数,压缩成一个,以达到降低时间的目的,就是这里不知道可供选择的选项,或者集合里面的元素,是不是也可以称之为状态,两者都很巧妙但是还是稍有不同的

这是之前的超时的代码,但是我觉得对位运算的理解是有帮助的

#include <bits/stdc++.h>

using namespace std;

const int N = 3e4 + 5;

int n, m, a[N][1005], ind[N], b[N], cnt, c[N];
int tot, ver[N], head[N], nxt[N];

void add(int x, int y) {
	ver[++tot] = y;
	nxt[tot] = head[x]; head[x] = tot;
}

void toposort() {
	queue<int> q;
	for (int i = 1; i <= n; i++) {
		if (!ind[i]) q.push(i);
	}
	while (!q.empty()) {
		int now = q.front(); q.pop();
		b[++cnt] = now;
		for (int i = head[now]; i; i = nxt[i]) {
			int y = ver[i];
			if (--ind[y] == 0) q.push(y);
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		ind[y]++;
	}
	toposort();
	reverse(b + 1, b + n + 1);
	for (int i = 1; i <= n; i++) {
		int x = b[i];
		a[x][(x - 1) / 31] |= (1 << ((x - 1) % 31));
		for (int j = head[x]; j; j = nxt[j]) {
			int y = ver[j];
			for (int k = 0; k < 1000; k++) {
				a[x][k] |= a[y][k];
			}
		}
		for (int j = 0; j < 1000; j++) {
			for (int k = a[x][j]; k; k -= (k & -k)) {
				c[x]++;
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		printf("%d\n", c[i]);
	}
	return 0;
}

之后是bitset的,这个stl第一次用,觉得真的很强

#include <bits/stdc++.h>

using namespace std;

const int N = 3e4 + 5;

int n, m, ind[N], b[N], cnt, c[N];
int tot, ver[N], head[N], nxt[N];
bitset<N> a[N];

void add(int x, int y) {
	ver[++tot] = y;
	nxt[tot] = head[x]; head[x] = tot;
}

void toposort() {
	queue<int> q;
	for (int i = 1; i <= n; i++) {
		if (!ind[i]) q.push(i);
	}
	while (!q.empty()) {
		int now = q.front(); q.pop();
		b[++cnt] = now;
		for (int i = head[now]; i; i = nxt[i]) {
			int y = ver[i];
			if (--ind[y] == 0) q.push(y);
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		ind[y]++;
	}
	toposort();
	reverse(b + 1, b + n + 1);
	for (int i = 1; i <= n; i++) {
		int x = b[i];
		a[x][x] = 1;
		for (int j = head[x]; j; j = nxt[j]) {
			a[x] |= a[ver[j]];
		}
	}
	for (int i = 1; i <= n; i++) {
		printf("%d", a[i].count());
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值