CF906C Party题解
今天来水一波题解……
题目传送门:
https://www.luogu.com.cn/problem/CF906C
理解题意
由于题目意思讲得很清楚,就因为懒惰直接复制了……
给你一堆一对对的关系,然后每一个关系对代表两个人认识。然后你每次可以选择一个人 \(i\) ,让 \(i\) 认识的所有人都相互认识,即 \(i\) 把介绍自己所有的朋友给其他人。然后现在问你最少需要选择多少个这样的 \(i\) ,使得所有的人都相互认识。
注意了,朋友的朋友并不一定互为朋友,否则就用并查集秒了(这看颜色紫紫的就知道不是这样)。
思路
用状压 DP 。
定义一个数组 \(dp_s\) , \(s\) 表示互为朋友的人的状态,例如 \(101001\) 表示第 \(1\) 个,第 \(4\) 个和第 \(6\) 个人的互为朋友,而 \(dp_s\) 表示实现 \(s\) 这个状态所需的介绍次数。
状态转移方程:
\(dp_{s \cup fr_i} \leftarrow \min(dp_{s \cup fr_i}, dp_s + 1)\)
\(dp_{s \cup fr_i}\) 表示在状态 \(s\) 中, \(i\) 介绍朋友后的状态, \(fr_i\) 表示 \(i\) 的朋友的集合,记住 \(i\) 也认识它自己!!!
那么初始化呢,因为i的朋友只需i来叫一次,so…… \(fr_i\) \(=\) \(1\) 。
注意如果输入的关系即可使所有人互为朋友,则应输出0,要特判。
代码
#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;
}
本蒟蒻洛谷第一遍题解 && AC 第一道紫题,如有不足请指教。
完结撒花