【容斥、状压dp】主旋律 题解

发布时间 2023-04-05 22:58:51作者: Otomachi_Una

【清华集训2014】主旋律 题解

神秘题。

题目简述

  • 给你一个有向图 \(G=(V,E)\)。求有多少 \(E\) 的子集 \(E'\) 使得新图 \(G'=(V,E')\) 是强连通图。

  • 强连通图的定义是任意两点 \(u,v\) 均存在 \(u\to v,v\to u\) 的路径。

  • \(n\leq 15,m\leq n\times(n-1)\)

解题思路

下面记点集 \(V_1\) 导出子图的答案为 \(f(V_1)\)。我们只要求 \(f(V)\)。我们考虑对每个点集 \(V_1\subset V\) 都求出 \(f(V_1)\)

正难则反,我们考虑他不是强连通图的方案。非强联通图缩点以后是一个 DAG,我们考虑枚举 DAG 当中所有入度为 \(0\) 的点,然后容斥即可。

更具体的,我们需要枚举所有入度为 \(0\) 的强连通分量 \(V'\subset V\),定义 \(g(V')\) 为选取的方案数,那么剩下的任务就是在 \(e^{\dagger}[V'\to V/V']\)\(e[V/V'\to V/V']\)。这些边是可以任意选取的。我们不需要担心重复的问题,这应该在 \(g\) 当中就解决掉。

\(^{\dagger}\) \(e[U\to V]\) 表示起点在点集 \(U\) 终点在点集 \(V\) 的有向边数量。

总结一下上面也就是:

\[f(V)=2^{e[V\to V]}-\sum_{V_1\subset V} g(V_1)\times 2^{e[V\to V/V']} \]

我们再考虑 \(g(V)\) 的计算方法。\(g(V)\) 的另一种理解是缩点后两两不联通的方案再去乘上容斥系数(偶数个连通分支为 \(-1\),奇数个为 \(1\)),我们去枚举一个连通分支就可以了。也就是:

\[g(V)=\sum_{V_1\subset V,u\in V_1} -f(V_1)\times g(V-V_1) \]

其中 \(u\) 是随意选取的一个 \(V\) 中的点。要注意的是 \(V_1\) 可能等于 \(V\),所以要先计算 \(f(V)\)

至此这题便做完了。时间复杂度 \(O(3^n\times n)\)

参考代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MOD=1e9+7;
int n,m;
int a[20];
ll pw[205],f[1<<16],g[1<<16];
int popcount(int x){int cnt=0;while(x)cnt++,x-=(x&-x);return cnt;}
int edge(int u,int v){
	int cnt=0;
	for(int i=0;i<n;i++)if(u>>i&1)cnt+=popcount(a[i]&v);
	return cnt;
}
void del(ll &x,ll y){x-=y;if(x<0)x+=MOD;}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v;
		a[u-1]|=(1<<v-1);
	}
	pw[0]=1;
	for(int i=1;i<205;i++) pw[i]=pw[i-1]*2%MOD;
	for(int i=1;i<(1<<n);i++){
		int t=(i&-i),k=i^t;// 固定一个点
		for(int j=(k-1)&k;j;j=(j-1)&k) del(g[i],g[k^j]*f[j|t]%MOD);
		if(t!=i) del(g[i],g[k]);
		f[i]=pw[edge(i,i)];
		for(int j=i;j;j=(j-1)&i) del(f[i],g[j]*pw[edge(i,i^j)]%MOD);
		g[i]+=f[i];g[i]%=MOD;
	}
	cout<<f[(1<<n)-1];
	return 0;
}