2020ICPC小米邀请赛决赛G Rikka with Game Theory

8 篇文章 0 订阅
题目:

给定一个 n n n个结点 m m m条边的无向图 G G G,现在给每个结点赋上一个数值 a a a,令 a u = m e x { a v ∣ ( u , v ) ∈ e d g e G } a_u=mex\{a_v|(u,v) \in edge_G\} au=mex{av(u,v)edgeG},问赋值的方案数。
( 1 ≤ n ≤ 17 , 0 ≤ m ≤ n ( n − 1 ) 2 ) (1 \le n \le 17,0 \le m \le \tfrac{n(n-1)}{2}) (1n17,0m2n(n1))

题解:

首先来看一下如何赋0,要同时满足赋0的结点之间不连边,可知我们要取出一个原图的独立集,且不赋0的结点一定有和赋0的结点的连边,所以是原图的一个极大独立集。那么再来看如何在赋完0的基础上赋1,可知是在剩余的点集中再取出一个极大独立集,以此类推,其他的赋值也类似。那么问题就可以转化了,变成每次取一个极大独立集,直到取完,问有多少种取法。
d p s dp_s dps表示点集 s s s的取极大独立集直至取完的取法,转移为 d p s = ∑ s ′ ⊂ s & & s ′ 是 极 大 独 立 集 d p s − s ′ dp_s=\sum_{s' \subset s \&\& s'是极大独立集}dp_{s-s'} dps=ss&&sdpss,边界为 d p 0 = 1 dp_0=1 dp0=1,答案为 d p ( 1 < < n ) − 1 dp_{(1<<n)-1} dp(1<<n)1。关于如何判断 s ′ s' s是极大独立集,预处理出每个集合的出边点集,如果 s ′ s' s集合的出边点集中不包括 s ′ s' s中的元素且 s − s ′ s-s' ss中的所有元素都在 s ′ s' s集合的出边点集中,那么 s ′ s' s是一个极大独立集。

复杂度: O ( 3 n ) O(3^n) O(3n)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=17;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
int g[maxn],w[1<<maxn];
ll dp[1<<maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		--u;--v;
		g[u]|=1<<v;
		g[v]|=1<<u;
	}
	for(int i=0;i<(1<<n);i++){
		for(int j=0;j<n;j++){
			if((i>>j)&1){
				w[i]|=g[j];
			}
		}
	}
	dp[0]=1;
	for(int i=1;i<(1<<n);i++){
		for(int j=i;j;j=(j-1)&i){
			if((w[j]&j)==0&&(w[j]&(i^j))==(i^j)){
				dp[i]+=dp[i^j];
			}
		}
	}
	printf("%lld\n",dp[(1<<n)-1]);
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值