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 第一道紫题,如有不足请指教。
完结撒花

posted @ 2024-07-20 15:39  Qian·JXのjoker  阅读(12)  评论(0编辑  收藏  举报