[Educational Round 17][Codeforces 762F. Tree nesting]

题目连接:762F - Tree nesting

题目大意:给出两个树\(S,T\),问\(S\)中有多少连通子图与\(T\)同构。\(|S|\leq 1000,|T|\leq 12\)

题解:考虑树的最小表示法(有关知识可戳https://www.byvoid.com/zhs/blog/directed-tree-bracket-sequence),求出\(T\)以不同点为根时所有的子树状态

   开始对树\(S\)进行\(DFS\),求出每个点的状态为\(t\)时的方案数,由于\(t\)还是由\(n\)个数字(括号序列)合并起来的,而且\(n\)不会太大,所以可以用二进制DP求解

   对当前点求解时,只需遍历其儿子,将儿子的解并入当前的状态即可。由于一个点可能有若干个形状相同的子树,所以要考虑去重,具体实现见代码

#include<bits/stdc++.h>
using namespace std;
#define N 1001
#define M 1<<12
#define MOD 1000000007
int len(int x){return 32-__builtin_clz(x);}
int Union(int x,int y){return (x<<len(y))|y;}
struct Tree
{
    int n,ans;
    int f[M][2];
    vector<int>d[N];
    map<int,int>num[N];
    map<int,vector<int> >mp;
    void read()
      {
      scanf("%d",&n);
      for(int i=2;i<=n;i++)
        {
        int u,v;
        scanf("%d%d",&u,&v);
        d[u].push_back(v);
        d[v].push_back(u);
        }
      }
    int dfs(int cur,int pre)
      {
      int res=1;
      vector<int>tmp;
      //tmp用来记录子树的状态 
      for(auto nxt:d[cur])if(nxt!=pre)
        tmp.push_back(dfs(nxt,cur));
      sort(tmp.begin(),tmp.end());
      for(auto x:tmp)res=Union(res,x);
      res<<=1;
      //res表示当前节点的最小表示 
      if(!mp.count(res))mp[res]=tmp;
      //将当前点对应的子树们也记录下来 
      return res;
      }
    void getID()
      {
      for(int i=1;i<=n;i++)
        dfs(i,0);
      //枚举以所有的点为根的情况 
      }
    void DP(int cur,int pre,const Tree &T)
      {
      for(auto nxt:d[cur])if(nxt!=pre)DP(nxt,cur,T);
      for(const auto &pi:T.mp)//枚举T中的所有状态 
        {
        auto &types=pi.second;
        int id=pi.first,n=types.size(),now=0,lst=1;
        for(int i=0;i<(1<<n);i++)f[i][0]=f[i][1]=0;
        f[0][0]=1;
        //id为当前枚举到的状态,n为当前状态拥有的子树数目,用滚动数组实现儿子们的合并 
        for(auto nxt:d[cur])if(nxt!=pre)
          {
          now^=1,lst^=1;
          for(int i=0;i<(1<<n);i++)
            f[i][now]=f[i][lst];
          for(int i=0;i<n;i++)
            if(num[nxt][types[i]])//num[i][j]表示点i的状态为j时方案有多少个 
              for(int j=(1<<n)-1;j>=0;j--)
                if(f[j][lst] && !((1<<i)&j) && !(i && types[i]==types[i-1] && !((1<<(i-1))&j)))
                  // (i && types[i]==types[i-1] && !((1<<(i-1))&j)代表的是
                  //当前枚举的子树和前一个子树同构 ,且前一个子树的状态未记录 
                  (f[(1<<i)|j][now]+=1ll*f[j][lst]*num[nxt][types[i]]%MOD)%=MOD;
          }
        if(f[(1<<n)-1][now])
          {
          num[cur][id]=f[(1<<n)-1][now];//集齐了所有子树即为对应num的答案 
          if(len(id)==2*T.n)(ans+=num[cur][id])%=MOD;//id的二进制长度为2m则说明一定是根节点的状态,加入答案 
          }
        }
      }
}S,T;
int main()
{
    S.read();
    T.read();
    T.getID();
    S.DP(1,0,T);
    printf("%d\n",S.ans);
}

 

posted @ 2019-04-25 21:42  DeaphetS  阅读(424)  评论(0编辑  收藏  举报