[BFS]Codeforces 906C Party题解

题目大意

给出一个 n n n个点 e e e条边的无向联通图,每次可以选中一个点,将这个点和它相邻的点缩成一个点,求最少需要多少次才能把图缩成一个点。

解题分析

首先要发现,以不同的顺序选中同样的点的结果其实是相同的,所以我们关注的就是选出哪些点,又由于 n ≤ 22 n\le22 n22,所以可以考虑二进制枚举选出的点集S,然后是结论:

如果S内点联通,且任意一个S外点都至少与一个S内点相连,那么S合法。

那么这两个条件如何判断?S外点与S内点相连可以事先用二进制求出每个点相连的点,用或运算就可以 O ( n ) O(n) O(n)判断,但是如何判断联通,只能BFS或DFS,但是复杂度是 O ( e ) O(e) O(e),最坏情况为 O ( n 2 ) O(n^2) O(n2),总复杂度为 O ( 2 n n 2 ) O(2^nn^2) O(2nn2),对于 n ≤ 22 n\le22 n22有点悬(虽然加一些神奇优化也能过),子集枚举只能 O ( 2 2 ) O(2^2) O(22),所以只能在BFS上优化,这里就要引用一个不常见的BFS小优化。

对于正常的BFS,需要开两个数组que和vis分别表示队列内元素和是否已进入队列,但如果对于此题 n n n特别小的情况下可以考虑两个都用二进制优化。每次先取出que内一个元素x,然后加入vis内,将x相连的边加入队列,这些都可以用异或或或(c++ ^或 |)运算解决,但是将相邻元素加入队列时要防止再度入队,所以可以对询问值再&~vs(vs二进制取反)。

具体代码如下

int vs=0,que=s&(-s); //初始选一个入队
while (que){
	int c=lst[que]; //lst[x]表示x的二进制最右是1的一位
	vs|=1<<(c-1); //打标记
	que^=1<<(c-1); //在队列中去除该元素
	que|=f[c]&s&~vs; //加入相邻元素
}

示例代码

题目传送门

#include
using namespace std;
const int maxn=(1<<22)+5;
int n,e,ans,lst[maxn],ct[maxn],f[30];
int main()
{
	freopen("party.in","r",stdin);
	freopen("party.out","w",stdout);
	scanf("%d%d",&n,&e);
	for (int i=1;i<(1<>1]+(i&1);
		lst[i]=(i&1)?1:lst[i>>1]+1;
	}
	for (int i=1;i<=n;i++) f[i]=1<<(i-1);
	for (int i=1,x,y;i<=e;i++){
		scanf("%d%d",&x,&y);
		f[x]|=(1<<(y-1)); f[y]|=(1<<(x-1));
	}
	ans=0;
	for (int i=1;i<=n;i++)
		if (f[i]!=(1<>(i-1)&1) S|=f[i];
		if (S!=(1<ct[s]) ans=s;
	}
	printf("%d\n",ct[ans]);
	for (int i=1;i<=n;i++)
		if (ans>>(i-1)&1) printf("%d ",i);
	return 0;
}

你可能感兴趣的:(其他题库,======图论======,BFS&DFS,BFS,Codeforces)