uoj #37. 【清华集训2014】主旋律
考虑原先求的是 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 方案数。
则有
子集反演
注意到,此时的 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;
}