[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);
}

 

转载于:https://www.cnblogs.com/DeaphetS/p/10771213.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值