人生第一次A,B层一块考rank2,虽然说分差没几分,但还是值得纪念。
T1 天空龙:
大神题,因为我从不写快读也没有写考场注释的习惯,所以不会做,全hzoi就kx会做,kx真大神级人物。
T2 巨神兵:
大神题,一看数据范围这么小,我们考虑状压,最傻逼的暴力思路是压边,但是这显然不行。正解是压点,设$f[s]$为当前选定点集状态为$s$的方案数。
我们考虑转移,当前选定的点集肯定是可以通过边和没有连过来的点相连构成新的方案。所以转移所以我们考虑枚举补集的子集$k$,设$cnt$为s与k两个点集相连的边数,那么转移为$f[s|k]+=f[s]*2^{cnt}$?不,这样会算重,因为比如我们先加A点,再加B点,和先加B点,再加A点是一样的。所以考虑容斥,容斥系数为$(-1)^{size[k]+1}$,蒟蒻博主不会正着推,但是这个可以证明是对的。我们设$g[i]$为转移了$i$层的系数,那么显然$g[0]=1$,然后$g$的递推式为$g[i]=\sum_{j=1}^{i}{C_{j}^{i}*(-1)^{j+1}*g[i-j]}$,我们要证的是$g[i]=1$,首先$g[i-j]$可以消掉,因为你由3层转移到5层,和从0层转移到2层是一样的然后
$\sum_{j=1}^{i}{C_j^i*(-1)^{j+1}}$
$=(-1)*(\sum_{j=0}^{i}{C_j^i*(-1)^{j}}-C_j^0*(-1)^0)$
$=(-1)*((1-1)^i-1)=1$ 证毕。
然后转移用了很多预处理,主要是找两个点集相连的边,我们首先预处理每个点与之相连点的状态,将它与上当前枚举的补集就好,然后还要预处理的是,每个二进制数中1的个数和每个取出来的1,是第几位,然后dp就好了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=1900000,mod=1e9+7; 5 int first[N],nex[N],to[N],tot,rc[N],sta[N],ma[5242880],f[5242880],n,m,qpow[N],re[N]; 6 void add(int a,int b){ 7 to[++tot]=b,nex[tot]=first[a],first[a]=tot; 8 } 9 signed main(){ 10 scanf("%lld%lld",&n,&m); 11 for(int i=1;i<=m;++i){ 12 int x,y; 13 scanf("%lld%lld",&x,&y); 14 add(x,y); 15 } 16 for(int i=0;i<=(1<<n);++i){ 17 ma[i]=ma[i>>1]+(i&1); 18 } 19 // for(int i=1;i<=1<<n;++i) cout<<"ma["<<i<<"]=="<<ma[i]<<" ";cout<<endl; 20 f[0]=qpow[0]=1; 21 for(int i=1;i<=n*n;++i) qpow[i]=(qpow[i-1]<<1)%mod; 22 for(int i=0;i<=n+1;++i) rc[i]=(i&1)?-1:1; 23 // for(int i=0;i<=n+1;++i) cout<<rc[i]<<" ";cout<<endl; 24 for(int i=1;i<=n;++i){ 25 re[1<<i-1]=i; 26 for(int j=first[i];j;j=nex[j]){ 27 sta[i]|=1<<to[j]-1; 28 } 29 } 30 int Max=1<<n,cnt=0,maxn=Max-1; 31 for(register int s=0;s^Max;++s){register int kl=~s; 32 for(register int i=kl&maxn;i;i=(i-1)&kl){ 33 cnt=0; 34 for(register int j=s;j;j-=j&-j) cnt+=ma[sta[re[j&-j]]&i]; 35 // cout<<cnt<<" "; 36 // cout<<f[s]%mod*qpow[cnt]%mod<<" "; 37 (f[s|i]+=rc[ma[i]+1]*f[s]%mod*qpow[cnt]%mod)%=mod; 38 } 39 } 40 printf("%lld\n",(f[maxn]+mod)%mod); 41 }