p4336 [SHOI2016]黑暗前的幻想乡

分析

对于所有边建Kirchhoff矩阵求出生成树个数

我们发现可能存在有多条边是一个人建得的情况

于是我们考虑容斥

因为数据范围很小所以我们可以枚举所有的不同人的集合

对于每个集合求出方案数

然后容斥即可

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<ctime>
#include<vector>
#include<set>
#include<map>
#include<stack>
using namespace std;
const int mod = 1e9+7;
int x[20][400],y[20][400],g[20][20],m[20],n;
inline int gs(){
    int i,j,k,ans=1;
    for(i=1;i<=n;i++)
      for(j=1;j<=n;j++)
        g[i][j]=(g[i][j]%mod+mod)%mod;
    for(i=1;i<=n;i++){
      for(j=i;j<=n;j++)
        if(g[j][i])break;
      if(j>n)return 0;
      if(j!=i)ans=mod-ans,swap(g[i],g[j]);
      for(j=i+1;j<=n;j++){
          while(g[j][i]){
            int t=g[i][i]/g[j][i];
            for(k=i;k<=n;k++)
              g[i][k]=(g[i][k]-1ll*g[j][k]*t%mod+mod)%mod;
            swap(g[i],g[j]);
            ans=mod-ans;
          }
      }
      ans=1ll*ans*g[i][i]%mod;
    }
    return ans;
}
int main(){
    int i,j,k,t,ans=0;
    scanf("%d",&n);
    for(i=1;i<n;i++){
      scanf("%d",&m[i]);
      for(j=1;j<=m[i];j++)
        scanf("%d%d",&x[i][j],&y[i][j]);
    }
    for(i=1;i<(1<<(n-1));i++){
      memset(g,0,sizeof(g));
      t=0;
      for(j=1;j<n;j++)
        if((1<<(j-1))&i){
          t++;
          for(k=1;k<=m[j];k++){
              int xx=x[j][k],yy=y[j][k];
              g[xx][xx]++;
              g[yy][yy]++;
              g[xx][yy]--;
              g[yy][xx]--;
          }
        }
      n--;
      int res=gs();
      n++;
      if((t&1)!=(n&1))ans=(ans+res)%mod;
        else ans=(ans-res+mod)%mod;
    
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2019-09-14 17:25  水题收割者  阅读(163)  评论(0编辑  收藏  举报