主旋律[清华集训2014 Day1]
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; }