把集合看成左边的点,图中的点看成右边的点,若集合$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()); } }