[2019牛客多校第四场][G. Tree]

题目链接:https://ac.nowcoder.com/acm/contest/884/G

题目大意:给定一个树\(A\),再给出\(t\)次询问,问\(A\)中有多少连通子图与树\(B_i\)同构。\(|A|\leq 2000,t\leq 10000, |B_i|\leq 12\)

题解:本题实际上是Codeforces 762F的加强版,关于这题的题解请戳这里

   本题做法与之前这道题类似,也是预处理出树的最小表示法后进行树形DP,但是由于这里有多达一万次询问,所以考虑预处理枚举所有点数不超过\(12\)的树并求出他们的最小表示。对于如何预处理所有满足条件的树,我的方法是假设当前树的大小为\(n\),将第\(n+1\)个点作为当前点或其祖先的儿子加入树中,并继续递归直至树的大小达到\(12\)。这样预处理后会发现点数不超过\(12\)的树只有不到\(8000\)个。接下来就是要对树\(A\)进行DP,设f[i][j]表示有多少以\(i\)为根节点的子树与编号为\(j\)的树同构,再令\(ans[j]=\sum_{i=1}^{n}f[i][j]\),对于每个询问的答案就是\(\sum ans[j]\),这里的\(j\)是树\(B\)以不同点为根时对应的编号。

   另外,在预处理的时候,我们同样可以预处理出当编号为\(j\)的树的根作为编号为\(i\)的树的根的儿子合并进来之后新树的编号,这样的合并关系只有不到\(14000\)组。这样对树\(A\)进行DP时就可以枚举所有这样的合并关系进行计算,将这一部分时间复杂度优化到\(O(14000n)\)

#include<bits/stdc++.h>
using namespace std;
#define N 2001
#define M 1<<12
#define MM 8001 
#define NN 16773121
#define MOD 1000000007
int len(int x){return 32-__builtin_clz(x);}
int Union(int x,int y){return (x<<len(y))|y;}
int cnt;
set<int>id[13];
int uni[MM][MM];
int num_to_id[NN];
int id_to_num[MM];
int f[N][MM];
vector<int>Id[13];
struct Tree
{
    int sz[N];
    int n,ans[NN];
    vector<int>d[N];
    vector<int>mp[MM];
    void read()
      {
      scanf("%d",&n);
      for(int i=1;i<=n;i++)
        d[i].clear();
      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)
      {
      sz[cur]=1;
      int res=1;
      vector<int>tmp;
      for(auto nxt:d[cur])if(nxt!=pre)
        tmp.push_back(dfs(nxt,cur)),sz[cur]+=sz[nxt];
      sort(tmp.begin(),tmp.end());
      for(auto x:tmp)res=Union(res,x);
      res<<=1;
      if(!num_to_id[res])cnt++,mp[cnt]=tmp,id_to_num[cnt]=res,num_to_id[res]=cnt;
      for(int i=0;i<tmp.size();i++)
        {
        int R=1;
        for(int j=0;j<tmp.size();j++)if(j!=i)
          R=Union(R,tmp[j]);
        R<<=1;
        uni[num_to_id[R]][num_to_id[tmp[i]]]=num_to_id[res];
        }
      id[sz[cur]].insert(num_to_id[res]);
      return res;
      }
    void getID()
      {
      for(int i=1;i<=n;i++)
        dfs(i,0);
      }
    void DP2(int cur,int pre)
      {
      sz[cur]=1;
      f[cur][1]=1;
      for(auto nxt:d[cur])if(nxt!=pre)
        {
        DP2(nxt,cur);
        for(int i=min(12,sz[cur]);i>=1;i--)
          for(auto ii:Id[i])
            {
            int v=f[cur][ii];
            if(!v)continue;
            for(int j=1;j<=min(12-i,sz[nxt]);j++)
              for(auto jj:Id[j])
                (f[cur][uni[ii][jj]]+=v*f[nxt][jj]%MOD)%=MOD;
            }
        sz[cur]+=sz[nxt];
        }
      for(int i=1;i<=min(12,sz[cur]);i++)
        for(auto ii:Id[i])
          (ans[ii]+=f[cur][ii])%=MOD;
      }
}S,T;
set<int>s;
int fa[13];
vector<int>d[13];
void fuck(int cur,int pre)
{
    fa[cur]=pre;
    for(int i=1;i<=12;i++)
      T.d[i]=d[i];
    T.n=cur;
    if(cur==12){T.dfs(1,0);return;}
    int x=cur;
    while(x!=0)
      {
      d[x].push_back(cur+1);
      fuck(cur+1,x);
      d[x].pop_back();
      x=fa[x];
      }
}
int main()
{
    fuck(1,0);
    for(int i=1;i<=12;i++)
      for(auto j:id[i])Id[i].push_back(j);
    S.read();
    S.DP2(1,0);
    int t;
    scanf("%d",&t);
    while(t--)
      {
      T.read();
      int ans=0;
      s.clear();
      for(int i=1;i<=T.n;i++)
        s.insert(T.dfs(i,0));
      for(auto x:s)(ans+=S.ans[num_to_id[x]])%=MOD;
      printf("%d\n",ans);
      }
    return 0;
}
View Code

 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值