[BFS]Codeforces 906C Party題解

題目大意

給出一個nn個點ee條邊的無向聯通圖,每次可以選中一個點,將這個點和它相鄰的點縮成一個點,求最少需要多少次才能把圖縮成一個點。

解題分析

首先要發現,以不同的順序選中同樣的點的結果其實是相同的,所以我們關注的就是選出哪些點,又由於n22n\le22,所以可以考慮二進制枚舉選出的點集S,然後是結論:

如果S內點聯通,且任意一個S外點都至少與一個S內點相連,那麼S合法。

那麼這兩個條件如何判斷?S外點與S內點相連可以事先用二進制求出每個點相連的點,用或運算就可以O(n)O(n)判斷,但是如何判斷聯通,只能BFS或DFS,但是複雜度是O(e)O(e),最壞情況爲O(n2)O(n^2),總複雜度爲O(2nn2)O(2^nn^2),對於n22n\le22有點懸(雖然加一些神奇優化也能過),子集枚舉只能O(22)O(2^2),所以只能在BFS上優化,這裏就要引用一個不常見的BFS小優化。

對於正常的BFS,需要開兩個數組que和vis分別表示隊列內元素和是否已進入隊列,但如果對於此題nn特別小的情況下可以考慮兩個都用二進制優化。每次先取出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<cstdio>
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<<n);i++){
		ct[i]=ct[i>>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<<n)-1) ans=(1<<n)-1;
	for (int s=1;s<(1<<n)-1;s++){
		int S=0;
		for (int i=1;i<=n;i++)
			if (s>>(i-1)&1) S|=f[i];
		if (S!=(1<<n)-1) continue;
		int vs=0,que=s&(-s);
		while (que){
			int c=lst[que];
			vs|=1<<(c-1);
			que^=1<<(c-1);
			que|=f[c]&s&~vs;
		}
		if (vs==s&&ct[ans]>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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章