『Tree nesting 树形状压dp 最小表示法』

<更新提示>

<第一次更新>


<正文>

Tree nesting (CF762F)

Description

有两个树 S、T,问 S 中有多少个互不相同的连通子图与 T 同构。由于答案 可能会很大,请输出答案模 1000000007 后的值。
两个连通子图 P、Q 称为互不相同的,当且仅当存在一个点 v∈S,使得 v∈P 与 v∈Q 恰好有一个成立。
图 G 与 H(在此题中 G 与 H 均为树)同构的定义为,存在一个一一映射 f, G 中任意一点 u 在 H 中都有唯一的点 v 与之对应;同样对 H 中任意一点 v 也有唯 一的在 G 中的点 u 与之对应。不妨设 f(u)=v, f-1 (v)=u(u∈G, v∈H),则还有对任意 A, B∈G,若 A 与 B 之间有边相连,则 f(A)与 f(B)在 H 中也有边相连;同样的,对 任意 A, B∈H,若 A 与 B 之间有边相连,则 f -1 (A)与 f -1 (B)在 G 中也有边相连。

Input Format

第一行一个正整数|S|表示树 S 的大小
接下来|S|-1 行都有两个正整数 ui , vi表示|S|中的边
接下来一行一个正整数|T|表示树 T 的大小
接下来|T|-1 行都有两个正整数 xi , yi表示|T|中的边

Output Format

一行,一个正整数表示答案模 1000000007 后的值

Sample Input

5 
1 2 
2 3 
3 4 
4 5 
3 
1 2 
2 3

Sample Output

3

解析

一道\(cf\)毒瘤题。

看到树同构于是想到树的最小表示法,也就是树的括号序列。以点\(x\)为根的子树的括号序列就是\(x\)所有子树的括号序列按字典序从小到大接起来,然后在最外面再套一对括号,这样就能唯一的表示一颗树。

我们先以\(T\)的每一个点为根,求一遍\(T\)的最小表示法,由于\(T\)的大小比较小,所以最小表示法序列还可以方便地用二进制数先存起来。

然后,我们考虑在\(S\)上用\(dp\)计数。\(S\)的连通子图和\(T\)同构,其大小首先一定等于\(T\)的大小,所以需要\(dp\)的范围也是很小的,考虑用状压\(dp\)。设\(f[x][S]\)代表以\(x\)为根的子树中,最小表示法序列匹配为\(S\)的方案数。

由同构的性质可知,要转移得到\(S\),其子最小表示法序列必然要是\(T\)中一样的子最小表示法,这启发我们在求\(T\)的最小表示法时,把最小表示法是由哪几个子最小表示法序列拼凑起来的也记录下来,这个我们可以用\(map+vector\)

接下来考虑如何\(dp\),对于一个节点\(x\),我们先将所有子节点先\(dp\),再利用子节点的\(dp\)值来更新。首先我们一定要枚举\(T\)的每一个最小表示法序列,然后我们就考虑现在节点\(x\)的儿子们和当前枚举到最小表示法序列的子序列们的一一匹配,如果能匹配上,就代表我们可以转移。

\(x\)的第\(i\)个儿子为\(ch_i\),当前最小表示法序列的第\(j\)个子序列为\(cur_j\),那么我们要看的就是\(f[ch_i][cur_j]\)的值,这就代表了儿子\(i\)与子序列\(j\)合法配对的方案。对于所有的\((i,j)\)满足\(f[ch_i][cur_j]>0\)都是我们需要考虑的,因为这些都代表了一种配对的可能。

如何利用子节点的\(f\)值转移得到当前节点呢?我们发现不能直接转移。可是再观察可以发现,这是另一个状压\(dp\)

设当前最小表示法序列是由\(len\)子最小表示法序列构成的,那么状态\(g[i][k]\)代表前\(i\)个儿子,子最小表示法序列中已经配对状态为\(k\)的配对方案数。由\(i\)来划分阶段,我们很快就可以列出状态转移方程:

\[g[i][k|2^j]=\sum_{j\not \in k} g[i-1][k]*f[ch_i][cur_j] \]

为了简便,我们可以用滚动数组的技巧把\(g\)化简成两个一维的数组。

注意,这个状压\(dp\)还有一点坑:若当前的最小表示法序列有两个相同的子序列,那么我们可能会对两个子序列重复计数(就是把\((10)_2\)\((01)_2\)这两种本质相同的选法都统计了一遍)。我们只需要最转移时特判一下即可。

好了,这样树形\(dp\)也基本上做完了,设\(x\)\(p\)个儿子,那么我们就可以用\(g[p][2^{len}-1]\)来更新\(f[x][S]\)。当然,如果\(S\)的长度为\(2m\)的话,就对应了\(T\)整棵树的某个最小表示法,可以直接累加到最后的答案里。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1020 , M = 15;
const int Mod = 1000000007;
int n,m,t1,t2,Head1[N],Head2[N];
int g[1<<M],p[1<<M],ans;
map < int , vector<int> > List;
map < int , int > f[N];
struct edge { int ver,next; } e1[N*2],e2[N*2];
inline void insert(int x,int y,int id)
{
    if ( id == 1 )
        e1[++t1] = (edge){y,Head1[x]} , Head1[x] = t1,
        e1[++t1] = (edge){x,Head1[y]} , Head1[y] = t1;
    if ( id == 2 )
        e2[++t2] = (edge){y,Head2[x]} , Head2[x] = t2,
        e2[++t2] = (edge){x,Head2[y]} , Head2[y] = t2;
}
inline int read(void)
{
    int x = 0 , w = 0; char ch = ' ';
    while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar();
    while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar();
    return w ? -x : x;
}
inline void input(void)
{
    n = read();
    for (int i=1;i<n;i++)
        insert( read() , read() , 1 );
    m = read();
    for (int i=1;i<m;i++)
        insert( read() , read() , 2 );
}
inline int lenth(int x) { return 32 - __builtin_clz(x); }
inline void connect(int &x,int y) { x = x << lenth(y) | y; }
inline int Ergodic(int x,int fa)
{
    int S = 1;
    vector < int > cur;
    for (int i=Head2[x];i;i=e2[i].next)
    {
        int y = e2[i].ver;
        if ( y == fa ) continue;
        cur.push_back( Ergodic( y , x ) );
    }
    sort( cur.begin() , cur.end() );
    vector < int > :: iterator it;
    for (it=cur.begin();it!=cur.end();it++)
        connect( S , *it );
    S <<= 1;
    if ( !List.count( S ) ) List[S] = cur;
    return S;
}
inline void dp(int x,int fa)
{
    vector < int > ch;
    for (int i=Head1[x];i;i=e1[i].next)
    {
        int y = e1[i].ver;
        if ( y == fa ) continue;
        ch.push_back( y ) , dp( y , x );
    }
    map < int , vector< int > > :: iterator it;
    for (it=List.begin();it!=List.end();it++)
    {
        vector < int > cur = it -> second;
        int S = it -> first , len = cur.size();
        memset( g , 0 , sizeof g );
        g[0] = 1;
        for (int i=0;i<ch.size();i++)
        {
            memcpy( p , g , sizeof g );
            for (int j=0;j<len;j++)
            {
                int val = f[ch[i]][cur[j]];
                if ( val == 0 ) continue;
                for (int k=(1<<len)-1;k>=0;k--)
                    if ( g[k] && !( k >> j & 1 ) && ( j == 0 || cur[j] != cur[j-1] || ( k >> (j-1) & 1 ) ) )
                        p[k|(1<<j)] = ( p[k|(1<<j)] + 1LL * g[k] * val % Mod ) % Mod;
            }
            memcpy( g , p , sizeof p );
        }
        if ( g[(1<<len)-1] )
        {
            f[x][S] = g[(1<<len)-1];
            if ( lenth(S) == (m<<1) )
                ans = ( ans + f[x][S] ) % Mod;
        }
    }
}
int main(void)
{
    input();
    for (int i=1;i<=m;i++)
        Ergodic( i , 0 );
    dp( 1 , 0 );
    printf("%d\n",ans);
    return 0;
}


<后记>

posted @ 2019-07-19 21:24  Parsnip  阅读(450)  评论(0编辑  收藏  举报