AtCoder Beginner Contest 187F Close Group | 状压dp、子集枚举

emmm,比较好的思想,忘记总结了,补上。

题目大意

给出一个 N N N个点的无向图,有 m m m条边,询问最少分成几个连通块,使得每个连通块内都是完全图?

题目思路

由于 N ≤ 18 N \le18 N18,所以考虑状压dp即可

显然, d p [ i ] dp[i] dp[i]代表i状态可以划分成的最小状态数,那么考虑 i i i的所有子状态 s s s,并考虑状态 s s s是否合法,如果合法,那么必然有: d p [ i ] = d p [ i ⊕ s ] + 1 dp[i] = dp[i⊕s]+1 dp[i]=dp[is]+1

所以考虑这些状态,将状态从小到大枚举,把当前状态所有可以组成完全图的子状态 s s s,都试着加入这个状态,看是否可以更新答案

枚举子集:

s s s是当前状态,那么必然 s s s的所有子集为:
f o r ( i n t   i = s ; i ; i = ( i − 1 ) & s ) for(int\ i=s;i;i = (i-1)\&s) for(int i=s;i;i=(i1)&s)

这里简单证明一下,一般枚举子集都是让当前状态 s s s 1 1 1,每次判断是否为当前状态的子集, ( i − 1 ) & s (i-1)\&s (i1)&s可以省去很多无用的状态,使其可以变到当前最大的与 s s s有交集的状态

证明一下这个写法的复杂度:

对于 N N N个元素集合的子集,包含 k k k个元素的集合数量有 C N k C_N^k CNk

对于数量为 k k k的集合,所有的子集有: 2 k 2^k 2k

所以所有的复杂度就是: ∑ k = 1 k = N C N k ∗ 2 k \sum_{k=1}^{k=N}C_N^k*2^k k=1k=NCNk2k = ∑ k = 1 k = N C N k ∗ 2 k ∗ 1 N − k \sum_{k=1}^{k=N}C_N^k*2^k*1^{N-k} k=1k=NCNk2k1Nk = 3 N 3^N 3N

了解复杂度之后,去写就可以了,过会儿来补一篇相似的

Code:

/*** keep hungry and calm CoolGuang!  ***/
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define dl(x) printf("%lld\n",x);
#define di(x) printf("%d\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17+7;
const ll maxn =5e5+700;
const ll mod= 1e9+7;
const ll up = 1e13;
const double eps = 1e-9;
template<typename T>inline void read(T &a){
    
    char c=getchar();T x=0,f=1;while(!isdigit(c)){
    
    if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){
    
    x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
int mp[25][25];
int b[25],dp[maxn],s[maxn];
int main(){
    
    
	read(n);read(m);
	for(int i=1;i<=m;i++){
    
    
		int x,y;read(x);read(y);
		mp[x][y] = mp[y][x] = 1;
	}
	int MAX = 1<<n;
	for(int i=1;i<=n;i++) b[i] = 1<<(i-1);
	for(int i=1;i<=n;i++)
		for(int k=1;k<=n;k++)
			if(mp[i][k]) b[i] |= 1<<(k-1);

	for(int i=1;i<MAX;i++){
    
    
		int flag = 0;
		for(int k=1;k<=n;k++){
    
    
			if(i>>(k-1)&1){
    
    
				if(!((b[k]|i)==b[k])){
    
    
					flag = 1;
					break;
				}
			} 
		}
		if(!flag) s[i] = 1;
	}

	for(int i=1;i<MAX;i++) dp[i] = 1e9+7;
	dp[0] = 0;
	for(int i=1;i<MAX;i++){
    
    
		for(int k=i;k;k=(k-1)&i){
    
    ///所有子集
			if(s[k]) dp[i] = min(dp[i],dp[i^k]+1);
		}
	}
	printf("%d\n",dp[MAX-1]);
	return 0;
}
/***
2 3
2 3 2
4 5 4 1 5
***/

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/112383286