[建图] CF986C AND Graph

posted on 2024-04-08 06:08:22 | under | source

无敌建图题。

如果接触过 \(\rm SOSdp\) 之类与子集有关的东东,那么看到 \(a \& b=0\),便可想到转换为子集的形式:\(b \in \neg a\)

思考怎么将枚举子集表现在图上。发现让每个点 \(u\),向所有是 \(u\) 子集且只比 \(u\) 少一位 \(1\) 的点连有向边即可。这样从 \(u\) 出发就恰好遍历了其所有子集。

为了体现 \(\neg a\),还需让 \(a\to \neg a\) 连边。问题解决了吗?没有,因为题目还限定了部分点无法访问,如果直接跳过的话不一定能遍历到所有子集。

于是建立分层图,将“枚举子集”这部分的图单独拎出来,记原图为 \(A\),新图为 \(B\)。按照此规则连边:

\(A(u)\to B(\neg u)\)。准备进入 \(B\) 枚举子集。

\(B(u)\to B(v)\)\(v\) 满足只比 \(u\) 少一个 \(1\)。开始在 \(B\) 中枚举。

\(B(u)\to A(u)\)。最后返回 \(A\),于是实现了 \(u\)\(u\&v=0\)\(v\) 之间连通。

代码

其实一个 bool 数组就够了,然后用 bitset 优化复杂度。

#include<bits/stdc++.h>
using namespace std;

const int N = 1 << 22, MA = N - 1;
int n, m, a[N + 5], ans; 
bitset<N << 1> vis; 

inline void dfs(int u){
	if(vis[u]) return ;
	vis[u] = 1;
	if(u < N) dfs((MA ^ u) + N);
	else{
		dfs(u - N);
		for(int i = 0; i < 22; ++i) if(((u - N) >> i) & 1) dfs(u - (1 << i));
	}
}
int main(){
	cin >> n >> m;
	for(int i = 0; i < N; ++i) vis[i] = 1;
	for(int i = 1; i <= m; ++i) scanf("%d", &a[i]), vis[a[i]] = 0;
	for(int i = 1; i <= m; ++i) if(!vis[a[i]]) ++ans, dfs(a[i]);
	cout << ans;
	return 0;
}
posted @ 2026-01-12 20:15  Zwi  阅读(5)  评论(0)    收藏  举报