[FJWC2020D1T2]赢家

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

题目

题目描述
对于一个 n n n 个点 m m m 条边的无重边无向图,求有多少种给边定向的方式,使得存在至少一个点 u u u 满足 1 , 2 1,2 1,2 均可到达 u u u

数据范围与提示
n ≤ 15 ,    m ≤ n ( n − 1 ) 2 n\le 15,\;m\le\frac{n(n-1)}{2} n15,m2n(n1)

思路

首先容斥,求不存在 u u u 使得 1 , 2 1,2 1,2 均可到达它。

考虑 1 , 2 1,2 1,2 在有向图中可以到达的点集分别是 S 1 , S 2 S_1,S_2 S1,S2,根据其定义, S 1 , S 2 S_1,S_2 S1,S2 与外部的连边都是指向 S S S 的。进而有, S 1 , S 2 S_1,S_2 S1,S2 之间没有连边

S 3 = U − S 1 − S 2 S_3={\Bbb U}-S_1-S_2 S3=US1S2,可以预处理出 h S h_S hS 表示 S S S 对应的导出子图的边乱选的方案数。只要再求出 f S , g S f_{S},g_S fS,gS 分别表示, S S S 对应的导出子图有多少种定向的方式,使得 1 / 2 1/2 1/2 能够到达所有点,我们就可以用 f S 1 ⋅ g S 2 ⋅ h S 3 f_{S_1}\cdot g_{S_2}\cdot h_{S_3} fS1gS2hS3 很轻松的计算出答案。

怎么求 f f f 呢?容斥,考虑有哪些点是 1 1 1 到达不了的。那么有
f S = h S − ∑ s ⫋ S h S − s ⋅ f s f_S=h_S-\sum_{s\subsetneqq S}h_{S-s}\cdot f_{s} fS=hSsShSsfs
总是 O ( 3 n ) \mathcal O(3^n) O(3n) 的过程。于是这道题就做完了。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
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 = 16;
const int Mod = 1e9+7;
int f[1<<MaxN], g[1<<MaxN], h[1<<MaxN]; 

int grap[MaxN], cnt[1<<MaxN];
int G[1<<MaxN], n, m;

int main(){
	n = readint(), m = readint(); readint();
	for(int i=0,u,v; i<m; ++i){
		u = readint()-1, v = readint()-1;
		grap[u] |= 1<<v, grap[v] |= 1<<u;
	}
	
	for(int i=1; i<(1<<n); ++i){
		cnt[i] = cnt[i^(i&-i)]+1;
		int id = 0; // lowbit
		for(; !(i>>id&1); ++id);
		G[i] = G[i^(1<<id)]|grap[id];
	}
	
	h[0] = 1; // no edge at all
	for(int S=1; S<(1<<n); ++S){
		int id = 0; // lowbit
		for(; !(S>>id&1); ++id);
		h[S] = h[S^(1<<id)];
		int t = cnt[S&grap[id]];
		for(int i=0; i<t; ++i)
			h[S] = (h[S]<<1)%Mod;
	}
	
	f[1] = 1; // only it self
	for(int S=3; S<(1<<n); S+=2){
		for(int s=S; s; s=(s-1)&S){
			int t = 1ll*f[s]*h[S^s]%Mod;
			f[S] = (f[S]+Mod-t)%Mod;
		}
		f[S] = (f[S]+h[S])%Mod;
	}
	
	g[2] = 1; // only itself
	for(int S=3; S<(1<<n); S=(S+1)|2){
		for(int s=S; s; s=(s-1)&S){
			int t = 1ll*g[s]*h[S^s]%Mod;
			g[S] = (g[S]+Mod-t)%Mod;
		}
		g[S] = (g[S]+h[S])%Mod;
	}
	
	int ans = 1;
	for(int i=0; i<m; ++i)
		ans = (ans<<1)%Mod;
	for(int S1=1; S1<(1<<n); S1+=2){
		int all = ((1<<n)-1)^S1; // left
		all ^= (all&G[S1]); // can't be reached
		for(int S2=all; S2; S2=(S2-1)&all){
			int t = 1ll*h[(1<<n)-1-S1-S2]*f[S1]%Mod;
			ans = (ans+Mod-1ll*t*g[S2]%Mod)%Mod;
		}
	}

	printf("%d\n",ans);
	return 0;
}

吐槽

我的思路 = = = 容斥(考虑同时有多个 u u u 使得 1 , 2 1,2 1,2 能够同时到达) + + + 正难则反(何时 1 , 2 1,2 1,2 无路径)。

题解思路 = = = 正难则反 + + + 容斥。

果然我的思路还是太局限了……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值