uoj #37. 【清华集训2014】主旋律

发布时间 2023-03-22 21:09:33作者: FxorG

考虑原先求的是 SCC 为 1 的方案数,这很困难!因为并没有能够转移到子问题的路径。

不妨考虑容斥,即 SCC 为 1 的方案数=所有方案数-SCC 不为 1 的方案数。

不妨先集合划分出 SCC,然后就变成了,内部的 SCC 子问题(此时因为钦定的 SCC 个数 >1,因此规模一定变小)以及外层的 DAG 计数。

不妨先考虑内层的 DAG 计数。

此时与原问题、原图没有一点关系!我们仅仅考虑一般有向图的删边后成为 DAG 这个子问题!!!

即当前图有若干个入度为 0 的点。考虑我们拓扑的过程,把这入度为 0 的点都搞一次之后删去其出边,之后一定又出现若干入度为 0 的点,否则因为其是个 DAG,则拓扑结束。也就是说,当我们钦定入度为 0 的点之后,我们将 DAG 计数转为了子规模的 DAG 计数。

即设 \(t(S)\) 为点集 \(S\) 的点导出子图的删边后成为 DAG 的方案数,\(f(T,S)\)\(S\) 中恰好入度为 0 的点集为 \(T\) 的 DAG 方案数。但恰好很难求,考虑钦定。即 \(g(T,S)\) 为钦定 \(S\) 中入度为 0 的点集为 \(T\) 的 DAG 方案数。

则有

\[g(T,S)=\sum_{T\subseteq R}f(R,S) \]

子集反演

\[f(T,S)\sum_{T\subseteq R}(-1)^{|R|-|T|}g(R,S) \]

\[t(S)=\sum_{T\subseteq S,T\not= \phi}f(T,S) \]

\[\sum_{T\subseteq S,T\not= \phi}f(T,S)=\sum_{T\subseteq S,T\not=\phi}\sum_{T\subseteq R\subseteq S,R\not=\phi}(-1)^{|R|-|T|}g(R,S)=...=\sum_{R\subseteq S,R\not=\phi}(-1)^{|R|+1}g(R,S) \]

注意到,此时的 DAG 计数仅仅与你钦定的入度为 \(0\) 的点集有关!

考虑回到原问题,即考虑钦定点集 \(T\) 作为入度为 \(0\) 的 SCC 的点集,但其内部的 SCC 还要集合划分!但我们只关心什么?因为是钦定!!!对于容斥系数而言,我们只关心其划分的 SCC 的个数的奇偶以及其方案数。

考虑记 \(f1(S)\)\(S\) 的点集划分出奇数个 SCC 的方案数,\(f0(S)\) 为偶数个。

\(dp(S)\) 为删边使得其为 DAG 的方案数。

那么我们有 \(dp(S)=2^{c(S,S)}-缩完点后的点数为 2 的子图数目\)

考虑钦定点集 \(T\) 作为入度为 \(0\) 的 SCC 点集,注意是钦定。那么就变成了 \(T\) 自己内部的子问题。又因为我们要求当前 SCC 个数 >1,所以并不会回到当前规模的问题!!!

那么要如何求 \(dp'(S)\),为 \(S\) 划分出 >1 个 SCC 的方案数/yiw

考虑缩点后的图,既然只关心 \(T\),那么钦定完之后,其他咋办!考虑 \(g(T,S)\) 在原问题的定义是钦定 \(T\) 中的点缩完点后在入度为 0 的 SCC 内!所以可以存在不属于 \(T\) 的点存在于入度为 \(0\) 的 SCC 内。因此钦定完,就直接瞎连了。

那么我们有 \(dp'(S)=\sum_{T\subseteq S,T\not=\phi}(f1(T)-f0(T))2^{c(T,S-T)+c(S-T,S-T)}\),不难发现,我们实际上就是在考虑其缩点后的情况。也就是说,本质就是在用 \(g\) 去贡献啦。只是对应的图一张是原图,一张是缩点后的图。并且,这个时候,我们当前数到的 SCC 数量一定 >1,这是因为,可能 =1 的情况当且仅当 \(T=S\),而我们钦定的 \(f1(S),f0(S)\) 此时均没有包含只有一个 SCC 的情况!我们并不会回到当前规模的问题,所以没事。

然后 \(f1,f0\) 的转移是平凡的,考虑钦定其中某一个点所在的 SCC 的点集即可转移。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int mod=(int)(1e9+7);
pair<int,int>e[1000];
int dp[(1<<15)],f0[(1<<15)],f1[(1<<15)],n,m,ou[(1<<15)],pw[1000];

inline int cal(int S,int T) {
	int res=0;
	while(S) {
		int x=(S&(-S));
		res+=__builtin_popcount(ou[x]&T);
		S-=x;
	}
	return res;
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		int x,y; cin>>x>>y; --x; --y;
		e[i]=make_pair(x,y);
		ou[1<<x]|=(1<<y);
	}
	pw[0]=1;
	for(int i=1;i<=m;i++) pw[i]=pw[i-1]*2%mod;
	for(int S=1;S<(1<<n);S++) {
		int p=__builtin_ffs(S);
//		for(int i=0;i<n;i++) {
//			if((S>>i)&1) {
//				p=i; break ;
//			}
//		}
		int SS=(S^p);
		for(int T=SS;;T=(T-1)&SS) {
			int TT=(T|p);
			f1[S]=(f1[S]+dp[TT]*f0[S^TT]%mod)%mod;
			f0[S]=(f0[S]+dp[TT]*f1[S^TT]%mod)%mod;
//			del[S]=(del[S]-dp[TT]*del[S^TT]%mod)%mod;
			if(!T) break ;
		}
		
		dp[S]=pw[cal(S,S)];
		for(int T=S;T;T=(T-1)&S) {
			dp[S]=(dp[S]-(f1[T]-f0[T]+mod)%mod*pw[cal(T,(S^T))]%mod*pw[cal((S^T),(S^T))]%mod)%mod;
			dp[S]=(dp[S]%mod+mod)%mod;
		}f1[S]=(f1[S]+dp[S])%mod;
//		cout<<dp[S]<<" "<<del[S]<<'\n';
	}
	cout<<(dp[(1<<n)-1]%mod+mod)%mod;
	return 0;
}