主旋律[清华集训2014 Day1]

响应主旋律的号召,大家决定让这个班级充满爱,现在班级里面有nn个男生。

如果aa爱着bb,那么就相当于aa和bb之间有一条aba→b的有向边。如果这nn个点的图是强联通的,那么就认为这个班级是充满爱的。

不幸的是,有一些不好的事情发生了,现在每一条边都可能被摧毁。我作为爱的使者,想知道有多少种摧毁的方式,使得这个班级任然充满爱呢?(说人话就是有多少边的子集删去之后整个图仍然强联通。)

第一行两个数nn和mm,表示班级里的男生数和爱的关系数。

接下来mm行,每行两个数aa和bb,表示男生aa爱着男生bb。同时aa不等于bb。

所有男生从11到nn标号。

同一条边不会出现两遍,但可能出现aa爱着bb,bb也爱着aa的情况,这是两条不同的边。

输出一行一个整数,表示对10^9+7取模后的答案。

SOL:

这是最难的一道题。我一脸蒙蔽。

首先我们发现正面做这道题很难。那么我们求答案的补集。

我们把图缩点后,若其为点数大于1的DAG,那么我们就认为其不合法。

利用容斥原理,DAG图的特征是有至少一个入度为0的点并且这个图不止一个点(这里及以下所说的点都是指求强连通后的 点),就根据这个进行容斥。

我们枚举每一个点集。我们发现其子集对答案的贡献是负的。那么我们枚举每个集合和其子集,每个元素有三种状态,0,1,或是由1枚举子集至0,那么复杂度就是O(3^n);

#include<bits/stdc++.h>
#define sight(c) ('0'<=c&&c<='9')
#define LL long long
#define mo 1000000007
#define L(x) ((x)&(-(x)))
#define SIZ (1<<16)+7
using namespace std;
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar()) x=x*10+c-48;
}
inline void add(LL &x,LL y){
    x=x+y; if (x>=mo) x%=mo; else if (x<0) (x%=mo)+=mo;
}
int n,m,x,y,in[SIZ],out[SIZ],cnt[SIZ];
LL po[SIZ],num[SIZ],f[SIZ],g[SIZ],M[SIZ],siz,v,ed,sum;
int main (){
    read(n); read(m);
    while (m--) {
        read(x),read(y);
        out[1<<x-1]|=1<<y-1; in[1<<y-1]|=1<<x-1;
    }
    po[0]=1; 
     for (int i=1;i<SIZ;i++) 
      po[i]=(po[i-1]<<1)%mo;
    siz=1<<n; 
    for (int i=1;i<siz;i++) num[i]=num[i-L(i)]+1;
    f[0]=g[0]=1; M[0]=mo-1;
    for (int i=1;i<siz;i++) {
        if (num[i]==1) {
            f[i]=M[i]=g[i]=1; continue;
        }
        x=L(i),v=i-x,ed=0;
        for (int j=0;j<n;j++) if (i&(1<<j)) ed+=num[out[1<<j]&i];
        f[i]=g[i]=po[ed],M[i]=0;
        for (int j=(v-1)&v,now=j|x;~j;j=(j-1)&v,now=j|x)  {
            add(M[i],-f[now]*M[i-now]%mo);
            if (!j) break;
        }
        cnt[i]=0; sum=M[i]*po[cnt[i]]%mo;
        for (int j=(i-1)&i;j;j=(j-1)&i) {
            int La=L(i-j),L=j|La;
            cnt[j]=cnt[L]-num[out[La]&(i-L)]+num[in[La]&j];
            add(sum,M[j]*g[i-j]%mo*po[cnt[j]]%mo);
        }
      add(f[i],-sum);add(M[i],f[i]);
    }
    printf("%lld\n",f[(1<<n)-1]); return 0;
    return 0;
}