我们发现,这其实就是一个完全图合并的问题。如果一个子图不是完全图,就一定要把它们合并起来。
我们考虑 \(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
标签:邻接矩阵,nmsk,CF906C,msk,集合,Party,我们,dp
From: https://www.cnblogs.com/jucason-xu/p/17379913.html