[CF906C]Party

122 篇文章 0 订阅
23 篇文章 0 订阅

题目

传送门 to CF

题意概要
现有 n n n m m m 边的简单无向连通图,每次可以选择一个点,将任意两个与该点相邻的点之间连上一条边。问最少操作多少次,使得整张图为完全图。

数据范围与提示
n ≤ 22 n\le 22 n22,而 m m m 无特殊限制。提示: C F CF CF 是少爷机。

思路

T I P TIP TIP:如果一个点集的导出子图是完全图,那么这个点集就是一个 “团”。

不难发现过程大概是这样的:选择一个点以后,对于一个包含该点的团,加入与该点相邻的点。一开始有很多个团,然后在不断的操作中变大(或者多个团合成了一个团),最终包含整张图。

我们是否可以 只追踪一个团的变化 呢?是可以的。这里给出一种简单的证明。

考虑何时不能只考虑一个团?那就是必须先操作当前的团之外的一个点,使得外面的两个点相连,然后操作团内的点。也就是说,操作团外的一个点 y y y,因为 y y y 和团内点 x x x、团外点 z z z 都有连边,就使得 x , z x,z x,z 相连。此时操作 x x x,就会把 z z z 拉入团。

可是,我们完全可以更改为先操作 x x x 再操作 y y y 。先操作 x x x 时, y y y 加入了 “团”,然后操作 y y y 就使 z z z 加入了 “团”。跟先操作 y y y 的效果是一样的。所以,我们先操作 x x x,也存在一种方式得到最优解。

也就是说,我们如果要让当前的 “团” 最终发展为大小为 n n n,那就不需要操作 “团” 之外的点,每次操作总是在让当前的 “团” 变大。

d p \tt dp dp 思路已经出现。用 f ( S ) f(S) f(S) 表示, S S S 集合中的点形成 “团” 所需最小操作次数。建议刷表法,枚举进行一次何种操作。输出方案存前驱即可。复杂度 O ( n 2 n ) \mathcal O(n2^n) O(n2n)

求 “团” 怎么求?任取一个 x ∈ S x\in S xS,则 S S S 是 “团” 的充要条件是, S − { x } S-\{x\} S{x} 是 “团” 且 S − { x } S-\{x\} S{x} 中任意一个点都与 x x x 相连。怎么判断是否 S S S 中每个点都与 x x x 相连?咱不是用状压的邻接矩阵吗,难道这都做不到?

代码

每个点最多操作一次,可知答案最大为 n n n 。实际上应该是 n − 2 n-2 n2,在一条链的情况下。

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 22;
int dp[1<<MaxN], g[1<<MaxN];
int pre[1<<MaxN], ans[1<<MaxN]; // 还原方案

void paint(int S){
	if(dp[S] == 0) return ; // 生而为人 我很抱歉
	paint(pre[S]), printf("%d ",ans[S]+1);
}

int main(){
	int n = readint(), m = readint();
	for(int i=1,a,b; i<=m; ++i){
		a = readint()-1, b = readint()-1;
		g[a] ^= 1<<b, g[b] ^= 1<<a;
	}
	for(int S=1; S<(1<<n); ++S){
		dp[S] = n+1; // 极大值
		for(int i=0; i<n; ++i)
			if(S>>i&1){
				int S_ = S^(1<<i);
				if((S_&g[i]) == S_)
					dp[S] = dp[S_];
				break; // 小剪枝
			}
	}
	for(int S=1; S<(1<<n); ++S)
	for(int i=0; i<n; ++i) if(S>>i&1)
		if(dp[S|g[i]] > dp[S]+1){
			dp[S|g[i]] = dp[S]+1;
			pre[S|g[i]] = S, ans[S|g[i]] = i;
		}
	printf("%d\n",dp[(1<<n)-1]);
	paint((1<<n)-1);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值