BZOJ3861 : Tree

把集合看成左边的点,图中的点看成右边的点,若集合$i$不包含$j$,则连边$i->j$,得到一个二分图,等价于求这个二分图的完备匹配个数。

设$f[i][j]$表示考虑了前$i$个集合,匹配了$j$个集合的方案数。

转移则是枚举当前集合是否匹配,然后设$g[i][j]$表示考虑了前$i$个内部点,匹配了$j$个集合的方案数。

最后方案数再除以每种集合出现次数的阶乘即可。

时间复杂度$O(n^2)$。

#include<cstdio>
#include<algorithm>
const int N=1010,P=1000000007;
int T,n,i,j,k,x,a[N],s[N],v[N],f[N][N],g[N][N],inv[N];unsigned long long h[N],w[N];
inline void up(int&a,int b){a+=b;if(a>=P)a-=P;}
int solve(){
  for(i=1;i<=n;i++)v[i]=0;
  for(i=1;i<=n;i++){
    scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
    for(w[i]=j=0;j<a[i];j++)scanf("%d",&x),w[i]^=h[x],v[x]++;
  }
  for(i=1;i<=n;i++)if(v[i]!=1)return 0;
  std::sort(w+1,w+n+1);
  for(f[1][0]=i=1;i<=n;i=j){
    for(j=i;j<=n&&w[i]==w[j];j++);
    f[1][0]=1LL*f[1][0]*inv[j-i]%P;
  }
  for(i=2;i<=n;i++){
    for(j=0;j<=i;j++)g[0][j]=f[i-1][j];
    for(j=1;j<=a[i];j++)for(k=0;k<=i;k++)g[j][k]=0;
    for(j=1;j<=a[i];j++)for(k=0;k<=i;k++)if(g[j-1][k]){
      if(k<i)up(g[j][k+1],1LL*g[j-1][k]*(i-1-k)%P);
      up(g[j][k],g[j-1][k]);
    }
    for(j=0;j<=i;j++)f[i][j]=g[a[i]][j],g[0][j]=1LL*f[i-1][j]*(s[i-1]-j)%P;
    for(j=1;j<=a[i];j++)for(k=0;k<=i;k++)g[j][k]=0;
    for(j=1;j<=a[i];j++)for(k=0;k<=i;k++)if(g[j-1][k]){
      if(k<i)up(g[j][k+1],1LL*g[j-1][k]*(i-1-k)%P);
      up(g[j][k],g[j-1][k]);
    }
    for(j=1;j<=i;j++)up(f[i][j],g[a[i]][j-1]);
  }
  return f[n][n];
}
int main(){
  for(i=1;i<N;i++)h[i]=h[i-1]*233+17;
  for(inv[0]=inv[1]=1,i=2;i<N;i++)inv[i]=1LL*(P-inv[P%i])*(P/i)%P;
  for(i=1;i<N;i++)inv[i]=1LL*inv[i]*inv[i-1]%P;
  while(~scanf("%d",&n)){
    if(!n)return 0;
    printf("Case #%d: %d
",++T,solve());
  }
}

  

原文地址:https://www.cnblogs.com/clrs97/p/6329731.html