今天來水一波題解……
理解題意
由於題目意思講得很清楚,就因為懶惰直接複製了……
給你一堆一對對的關係,然後每一個關係對代表兩個人認識。然後你每次可以選擇一個人i,讓i認識的所有人都相互認識,即i把介紹自己所有的朋友給其他人。然後現在問你最少需要選擇多少個這樣的i,使得所有的人都相互認識。
注意了,朋友的朋友並不一定互為朋友,否則就用並查集秒了(這看顏色紫紫的就知道不是這樣)。
思路
用狀壓DP
定義一個陣列DP[s],s表示互為朋友的人的狀態,例如101001表示第1個,第4個和第6個人的互為朋友,而DP[s]表示實現s這個狀態所需的介紹次數。
狀態轉移方程:
dp[s|fr[i]]=min(dp[s|fr[i]],dp[s]+1);
dp[s|fr[i]]表示在狀態s中,i介紹朋友後的狀態,fr[i]表示i的朋友的集合,記住i也認識它自己!!!
那麼初始化呢,因為i的朋友只需i來叫一次,so……fr[i]=1。
注意如果輸入的關係即可使所有人互為朋友,則應輸出0,要特判。
接下來貼上我醜陋的程式碼
ACcode
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=(1<<22)+1000;
int fr[N];//fr[i]-->i的朋友
int s[N];//人的集合
int dp[N];//使集合i的人認識所需介紹次數
int q[N];//q[s]=i表示s的轉態這次由i介紹得到
int pre[N];//存s經過這次i介紹之前的狀態
const int inf=0x3f3f3f3f;
void dfs(int x)
{
if(pre[x]) dfs(pre[x]);
cout<<q[x]<<" ";//在回溯時輸出
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
memset(dp,0x3f,sizeof dp);
cin>>n>>m;
for(int i=1;i<=n;i++) fr[i]+=1<<(i-1);//自己認識自己
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
fr[u]+=1<<(v-1);
fr[v]+=1<<(u-1);
//v,u互為朋友
}
if(m==n*(n-1)/2)//原本互為朋友,特判
{
cout<<0;
return 0;
}
for(int i=1;i<=n;i++)
{
dp[fr[i]]=1;//i介紹fr[i]需要一步
q[fr[i]]=i;//fr[i]由i介紹
}
for(int s=0;s<=(1<<n)-1;s++)//列舉狀態
{
if(dp[s]==inf) continue;//當前集合無法達到
for(int i=1;i<=n;i++)
{
if((s&(1<<i-1)))//滿足s包含i
{
if(dp[s|fr[i]]>dp[s]+1)
{
dp[s|fr[i]]=dp[s]+1;//更新
q[s|fr[i]]=i;//存當前的介紹人
pre[s|fr[i]]=s;//存i介紹前的狀態
}
}
}
}
cout<<dp[(1<<n)-1]<<endl;//輸出步數
dfs((1<<n)-1);//輸出介紹人
return 0;
}