清华集训2014 主旋律

题面

lk好菜啊....

大部分还是看Sengxian的博客学的......

题解

设:

\(f_S\)表示S中的点组成一个联通块的方案数。

\(g_S\)表示S中点组成一个DAG(不止一个联通块)的方案数。

\(h_s\)表示首尾都在S中的边数。

那么显然\(f_S=2^{h_S}-g_S\)

\(f_S\)很难算,“正难则反”我们可以通过计算\(g_S\)来得到\(f_S\)

我们设\(c_S\)表示S中点组成一些联通块(块之间没有边)的方案数。

\(g_S\)计算时可以通过枚举出度为0的联通块来计算。这就需要用到容斥。

我们可以直接把\(c_S\)的定义改成 S中点组成奇数个联通块方案数 - S中点组成偶数个联通块方案数。

注意这个方案指的是留下的边集而不是分成的联通块。

就可以通过选定一个点,枚举包含这个点的联通块然后计算\(c_S\)

显然\(C_\emptyset=0\) \[ c_S=f_S-\sum_{\substack{T\subset S\\k\in T}}f_Tc_{S-T} \]

表示枚举包含点k的联通块T,然后计算。

这个-表示多了一个联通块所以取反。

通过枚举出度为0的联通块集合T,我们可以得到\(g_S\)的递推公式:

\[ g_S=\sum_{\substack{T\subset S\\T\not=\emptyset}}2^{h_{S-T}+e_{S-T,T}}c_{T}\\ f_S=2^{h_S}-\sum_{\substack{T\subset S\\T\not=\emptyset}}2^{h_{S-T}+e_{S-T,T}}c_{T} \]

\(h\)刚刚定义了,\(e_{S,T}\)表示从S集合中点到T集合中点的边数。

这个表示,枚举出度为0的联通块,其它的点之间的边和到这些联通块上的边都是可选可不选的,因为有容斥。

慢着!有问题!

\(g_S\)的公式中,\(S=T\)时,\(c_T\)会包含\(f_S\) 也就是只有一个联通块的情况,但是\(g_S\)\(f_S\)是对立的(不止一个联通块),并且这样的话计算\(f_S\)会需要\(c_S\)的值,\(c_S\)又需要\(f_S\)的值,这就会出问题。

那就\(c_S\)先不加\(f_S\),算出\(g_S\)当然还有\(f_S\)以后再加就可以了。

对于\(h_{S}\)\(e_{S,T}\),可以直接\(O(n2^n)\)\(O(3^n)\)预处理。

由于需要枚举子集,\(f_S\)\(c_S\)的计算也是\(O(3^n)\)的。

所以直接\(O(3^n)\)完美的AC了。

/*
Author: CNYALI_LK
LANG: C++
PROG: scon.cpp
Mail: cnyalilk@vip.qq.com
*/
#include<bits/stdc++.h>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define DEBUG printf("Passing [%s] in LINE %lld\n",__FUNCTION__,__LINE__)
#define Debug debug("Passing [%s] in LINE %lld\n",__FUNCTION__,__LINE__)
#define all(x) x.begin(),x.end()
#define x first
#define y second
using namespace std;
const double eps=1e-8;
const double pi=acos(-1.0);
typedef long long ll;
typedef pair<ll,ll> pii;
template<class T>ll chkmin(T &a,T b){return a>b?a=b,1:0;}
template<class T>ll chkmax(T &a,T b){return a<b?a=b,1:0;}
template<class T>T sqr(T a){return a*a;}
template<class T>T mmin(T a,T b){return a<b?a:b;}
template<class T>T mmax(T a,T b){return a>b?a:b;}
template<class T>T aabs(T a){return a<0?-a:a;}
#define min mmin
#define max mmax
#define abs aabs
ll read(){
    ll s=0,base=1;
    char c;
    while(!isdigit(c=getchar()))if(c=='-')base=-base;
    while(isdigit(c)){s=s*10+(c^48);c=getchar();}
    return s*base;
}
const ll N=19,M=33333,K=14456789,p=1000000007;
ll ein[M],a[N][N],f[M],g[M],et[N][M],eto[K],_2t3[M];//g 指的就是刚才那个c
ll logs[M],p2[M];
int main(){
#ifdef cnyali_lk
    freopen("scon.in","r",stdin);
    freopen("scon.out","w",stdout);
#endif
    ll n,m,t;
    n=read(),m=read();t=1<<n;
    for(ll i=1;i<=m;++i)a[read()-1][read()-1]=1;
    for(ll i=0;i<n;++i)logs[1<<i]=i;
    for(ll i=0;i<n;++i)for(ll j=1;j<t;++j){
        et[i][j]=et[i][j^(j&(-j))]+a[i][logs[j&(-j)]];
    }
    p2[0]=1;
    for(ll i=1;i<t;++i){_2t3[i]=_2t3[i>>1]*3+(i&1);p2[i]=p2[i-1]*2%p;}
    for(ll i=1;i<t;++i){
        for(ll j=0;j<n;++j)if(i&(1<<j))ein[i]+=et[j][i];
//      printf("%lld\n",ein[i]);
    }
    for(ll i=1;i<t;++i)for(ll j=(t-1)^i;j;j=(j-1)&((t-1)^i)){
        eto[_2t3[i]+_2t3[j]+_2t3[j]]=eto[_2t3[i^(i&(-i))]+_2t3[j]+_2t3[j]]+et[logs[i&(-i)]][j];
    }
    for(ll i=1;i<t;++i){
        ll ka=i&(-i),kb=i^ka;
        g[i]=0;
        for(ll j=kb;j;j=(j-1)&kb){
            g[i]=(g[i]-g[j]*f[i-j]%p+p)%p;
        }
        f[i]=p2[ein[i]];
        for(ll j=i;j;j=(j-1)&i){
            f[i]=(f[i]-g[j]*p2[ein[i-j]+eto[_2t3[i-j]+2*_2t3[j]]]%p+p)%p;
        }
        g[i]=(g[i]+f[i])%p;
    }
    printf("%lld\n",f[t-1]);
    return 0;
}