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 }
View Code

 

posted @ 2017-10-16 11:31  Nawox  阅读(183)  评论(0编辑  收藏  举报