BZOJ[4013] [HNOI2015]实验比较
树DP+组合数
网上题解很多,这里就放个有注释的代码
CODE
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <iostream> 6 #include <algorithm> 7 # define maxn 110 8 # define mod 1000000007 9 using namespace std; 10 typedef long long LL; 11 LL C[maxn][maxn]; 12 void GetC(){ 13 C[0][0]=1; 14 for(int i=1;i<=100;i++){ 15 C[i][0]=1; 16 for(int j=1;j<=i;j++){ 17 C[i][j]=C[i-1][j-1]+C[i-1][j]; 18 C[i][j]%=mod; 19 } 20 } 21 } 22 int n,m,N; 23 struct node{ 24 int u,v,nxt; 25 }g[10010]; 26 int adj[maxn],e,ru[maxn]; 27 void add(int u,int v){ 28 g[e]=(node){u,v,adj[u]}; 29 adj[u]=e++; 30 } 31 int fa[maxn]; 32 int find_fa(int x){ 33 if(x==fa[x]) return x; 34 return fa[x]=find_fa(fa[x]); 35 } 36 bool vis[maxn]; 37 void init(){ 38 scanf("%d%d",&n,&m); 39 for(int i=1;i<=n;i++) fa[i]=i; 40 memset(adj,-1,sizeof(adj)); 41 int x,y; char s[3]; 42 for(int i=1;i<=m;i++){ 43 scanf("%d%s%d",&x,&s,&y); 44 int a=find_fa(x),b=find_fa(y); 45 if(s[0]=='='){ 46 fa[b]=a; 47 } 48 else{ 49 add(a,b); ru[b]++; 50 } 51 } 52 for(int i=1;i<=n;i++){ 53 int a=find_fa(i); 54 if(!vis[a]){ 55 vis[a]=1; N++; 56 if(!ru[a]) add(0,a); 57 } 58 } 59 } 60 int q[maxn],head,tail; 61 bool check(){ 62 int cnt=0; 63 memset(vis,0,sizeof(vis)); 64 for(int i=1;i<=n;i++){ 65 int a=find_fa(i); 66 if(!vis[a] && !ru[a]) q[tail++]=a,vis[a]=1; 67 } 68 while(head<tail){ 69 int k=q[head++]; cnt++; 70 for(int i=adj[k];i!=-1;i=g[i].nxt){ 71 int v=g[i].v; ru[v]--; 72 if(!ru[v]) q[tail++]=v; 73 } 74 } 75 if(cnt!=N){cout<<"0"<<endl; return 0;} 76 return 1; 77 } 78 LL f[maxn][maxn]; 79 // f[i][j] 表示以i节点为根的子树合成的序列中有j个小于号的方案数 80 //能出现小于号数的变化是因为两个字数中的序列可以合并 81 //例如: 1<2 ; 3<4; 可以合并成 1=3<2=4 82 LL p[maxn]; 83 int size[maxn]; 84 void dfs(int x){ 85 if(adj[x]==-1){f[x][1]=1; size[x]=1; return;} 86 size[x]=0; 87 bool ok=0; 88 for(int i=adj[x];i!=-1;i=g[i].nxt){ 89 int v=g[i].v; dfs(v); 90 if(!ok){ //遇到第一个儿子可以直接复制过来, 91 ok=1; 92 for(int j=0;j<=size[v];j++){ 93 f[x][j]+=f[v][j]; 94 } 95 size[x]+=size[v]; 96 continue; 97 } 98 memset(p,0,sizeof(p)); 99 for(int j=1;j<=size[x];j++){ //枚举i节点子树中已合并的 100 for(int k=1;k<=size[v];k++){ //枚举儿子 101 if(!f[x][j] || !f[v][k]) continue; 102 for(int h=max(j,k);h<=j+k;h++){ 103 p[h]=(p[h]+f[x][j]*f[v][k]%mod*C[h][j]%mod*C[j][k-(h-j)]%mod)%mod; 104 // 把j+k个合并成h个,相当于把1,0放入h个盒子,相同的不能在一个盒子 105 //不能有空盒子的方案数 106 //那么可以选j个盒子,把1全部放进去 107 //然后剩下的空盒子用k补全 108 //最后用剩下的0 放到有1的盒子里 109 } 110 } 111 } 112 memcpy(f[x],p,sizeof(f[x])); 113 size[x]+=size[v]; 114 } 115 size[x]++; 116 if(x){//不是虚根 117 for(int i=size[x];i>=1;i--) f[x][i]=f[x][i-1]; 118 //父亲上的小于号是没有办法合并的,所以之前的所有方案数的j都要+1 119 } 120 } 121 void work(){ 122 LL ans=0; 123 dfs(0); 124 for(int i=1;i<=n;i++) ans+=f[0][i],ans%=mod; 125 cout<<ans<<endl; 126 } 127 int main(){ 128 // freopen("a.in","r",stdin); 129 init(); 130 GetC(); 131 if(check()) work(); 132 }