题目大意
给定一些关系(u,v)代表两个人是朋友。每次可以选择一个人i,使i的朋友都相互变成朋友。问最少需要选择多少人,使得所有的人都能够相互认识。
状压dp
注意到特殊的数据范围,考虑状压。
首先明确两个结论:
- 答案与合并顺序无关
- 对图G={V,E}中一个完全子图G′={V′,E′}中的点x∈V′进行操作后,其所在的完全子图G″={V′+Vx,E′+Ex},Ex={x,Vx}∈E。换句话说就是操作集合内的点能使集合变大。
第一条是因为操作点x之后∀(x,yi),yi之间都存在边,那么下一步选取yi时会把其他的yj也都连在一起;第二条比较容易理解。
那么用f[i]表示i的二进制状态下,这些人相互认识的最小代价。
于是转移就是经典的状压转移;顺便再记录一下转移的位置就好了。
需要注意的细节是原图是否已经连通,那么这个就是预处理的时候再判断一下。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
const int maxn=35;
const int maxs=5000035;
const int INF=0x3f3f3f3f;
bool fnd;
int n,m,mx;
int s[maxn],f[maxs],fa[maxs],pr[maxs];
void fndScheme(int st)
{
if(fa[st])
fndScheme(fa[st]);
printf("%d ",pr[st]);
}
int main() {
while(~scanf("%d%d",&n,&m))
{
memset(f,0x3f3f3f3f,sizeof(f));
mx=1<<n;
for(int i=0;i<n;i++)
{
s[i]=1<<i;
}
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x--;y--;
s[x]|=(1<<y);s[y]|=(1<<x);
}
fnd=1;
for(int i=0;i<n;i++)
{
f[s[i]]=1;
pr[s[i]]=i+1;
if(s[i]!=mx-1)
fnd=0;
}
if(fnd==1)
{
printf("0\n");
return 0;
}
for(int i=1;i<mx;i++)
{
if(f[i]!=INF)
{
for(int j=0;j<n;j++)
{
if(((i>>j)&1)!=0 && f[i]+1<f[i|s[j]])
{
f[i|s[j]]=f[i]+1;
fa[i|s[j]]=i;
pr[i|s[j]]=j+1;
}
}
}
}
printf("%d\n",f[mx-1]);
fndScheme(mx-1);
printf("\n");
}
return 0;
}