CF906C - Party

发布时间 2023-05-07 19:33:28作者: jucason_xu

我们发现,这其实就是一个完全图合并的问题。如果一个子图不是完全图,就一定要把它们合并起来。

我们考虑 \(dp_{msk}\) 表示只对当前集合 \(msk\) 的点进行操作,使得 \(msk\) 集合是完全图的最小步数。初始状态是枚举所有的 \(msk\) 检测是否是完全图。然后我们每次枚举和当前集合的加入集合 \(nmsk\),找到 \(msk\cap nmsk\),只要有交集,就可以转移到 \(msk\cup nmsk\)。但是这样显然复杂度是寄的。

考虑优化,我们发现我们挂的主要原因就是每次都用两个集合同时贡献,我们考虑能不能每次都只选当前集合里的点。

我们发现是可以的。假设我们在原先的方案中,先对 \(S\)\(T\) 进行一系列操作,然后一次操作 \(x\) 合并。那么我们考虑先把 \(S\) 造出来,然后进行步骤 \(x\),然后进行 \(T\) 上的操作。我们发现这样显然是等价的,因为在执行步骤 \(x\) 之后,\(S\) 中的任意元素都和 \(x\) 等价,也就一定能建出完全图。

我们就证明了每次都只在 \(msk\) 里选点是正确的。

然后考虑如何实现。我们可以压位存储一个邻接矩阵。然后对于 \(msk\),只要把里面的所有元素的邻接矩阵与起来就能 \(O(n)\) 判断是不是完全图。然后枚举更新的点,更新的集合就是这个元素的邻接矩阵。

最后记录来源回溯即可。

复杂度 \(O(n2^n)\)

int n,m,a,b,e[22],dp[1<<22];
int fr[1<<22],fp[1<<22];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	rp(i,m){
		cin>>a>>b;a--,b--;
		e[a]|=(1<<b);
		e[b]|=(1<<a);
	}
	rd(i,n)e[i]|=(1<<i);
	rd(msk,(1<<n))dp[msk]=1e9;
	rd(msk,(1<<n)){
		int cur=msk;
		rd(i,n)if(msk>>i&1)cur&=e[i];
		if(cur==msk)dp[msk]=0;
		rd(i,n)if(msk>>i&1){
			if(dp[msk|e[i]]>dp[msk]+1){
				dp[msk|e[i]]=dp[msk]+1;
				fr[msk|e[i]]=msk;
				fp[msk|e[i]]=i;
			}
		}
	}cout<<dp[(1<<n)-1]<<endl;
	int cur=(1<<n)-1;
	while(dp[cur]){
		cout<<fp[cur]+1<<" ";
		cur=fr[cur];
	}cout<<endl;
	return 0;
}
//Crayan_r