UOJ37 【清华集训2014】主旋律

Link
然后枚举缩点之后的DAG的情况,考虑计算可行的边集方案数,再递归乘上所有scc的导出子图的答案。
计算可行的边集方案数可以考虑dp,设\(f_s\)表示点集\(s\)的导出子图中使得子图形成一个DAG的边集方案数,那么可以得到转移

\[f_s=\sum\limits_{t\subseteq s\wedge t\ne\emptyset}(-1)^{|t|+1}f_{s-t}2^{cnt_{t,s-t}} \]

其中\(cnt_{s,t}\)表示的是起点在\(s\)中,终点在\(t\)中的边数。
\((-1)^{|t|+1}\)是容斥系数,因为我们不能保证每个点都连出至少一条边,直接计算会算重。
不难发现子集\(t\)的贡献系数仅与其scc数量的奇偶性有关,奇数为正偶数为负。
那么我们就没有必要枚举缩点后的结果了,设\(f_s\)表示\(s\)的导出子图中使得图强联通的边集方案数,\(g_s,h_s\)表示\(S\)的导出子图中的使得图中形成奇/偶个scc,且scc之间相互没有边的边集方案数。
那么我们有

\[f_s=2^{cnt_{s,s}}-\sum\limits_{t\subseteq s,t\ne\emptyset}(g_t-h_t)2^{cnt_{t,s-t}+cnt_{s-t,s-t}}\\ g_s=\sum\limits_{t\subseteq s,t\ne\emptyset}f_th_{s-t}\\ h_s=\sum\limits_{t\subseteq s,t\ne\emptyset}f_tg_{s-t} \]

\(s=t\)的部分先转移\(f\)再转移\(g,h\)即可。

#include<cstdio>
using i64=long long;
const int N=1<<15,P=1000000007;
i64 to[N],e[N],f[N],g[N],pw[256];
int read(){int x;scanf("%d",&x);return x;}
void dec(i64&a,i64 b){a-=b,a+=a>>63&P;}
int calc(int s,int t)
{
    int ans=0;
    for(;s;s^=s&-s) ans+=__builtin_popcount(to[s&-s]&t);
    return ans;
}
int main()
{
    int n=read(),m=read();
    for(int i=1,u,v;i<=m;++i) u=1<<(read()-1),v=1<<(read()-1),to[u]|=v;
    for(int i=1;i<1<<n;++i) e[i]=calc(i,i);
    for(int i=pw[0]=1;i<=210;++i) pw[i]=2*pw[i-1]%P;
    for(int i=1,j,k,s;i<1<<n;(g[i]+=f[i])%=P,++i)
    {
	for(k=i&-i,s=i^k,j=(s-1)&s;j;j=(j-1)&s) dec(g[i],g[i^j^k]*f[j|k]%P);
	if(i^k) dec(g[i],g[i^k]);
	for(f[j=i]=pw[e[i]];j;j=(j-1)&i) dec(f[i],g[j]*pw[e[i^j]+calc(j,i^j)]%P);
    }
    printf("%lld",f[(1<<n)-1]);
}
posted @ 2020-06-05 22:36  Shiina_Mashiro  阅读(321)  评论(0编辑  收藏  举报