emmm,比较好的思想,忘记总结了,补上。
题目大意
给出一个 N N N个点的无向图,有 m m m条边,询问最少分成几个连通块,使得每个连通块内都是完全图?
题目思路
由于 N ≤ 18 N \le18 N≤18,所以考虑状压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[i⊕s]+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=(i−1)&s)
这里简单证明一下,一般枚举子集都是让当前状态 s s s减 1 1 1,每次判断是否为当前状态的子集, ( i − 1 ) & s (i-1)\&s (i−1)&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=NCNk∗2k = ∑ 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=NCNk∗2k∗1N−k = 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
***/