星星之火

[JZOJ4272] [NOIP2015模拟10.28B组] 序章-弗兰德的秘密 解题报告(树形DP)

Description

背景介绍
弗兰德,我不知道这个地方对我意味着什么。这里是一切开始的地方。3年前,还是个什么都没见过的少年,来到弗兰德的树下,走进了封闭的密室,扭动的封尘已久机关,在石板上知道了这个世界最角落的最阴暗的东西。那种事情,从未忘怀,从未动摇,我还记得,那一天,我,里修,第一次拔起了剑……

弗兰德的密室里,机关上方画着两棵树的字样,机关下方是一个有数字的刻度……
弗兰德最高的两棵树,只要知道两棵树的共同的相似度就行了……
给定两棵有根树,可以任意删除两棵树上的节点(删除一棵节点必须保证该节点的子树内的所有节点也必须要被删除,换一种说法,删除后的树必须联通并形成一棵树,且根节点不能被删除),使得删除后的两棵树同构,这两棵树有一个共同大小,即树的size,最大化同构的树的size即为机关的答案……

注:两棵同构的树要满足以下条件:
1、两棵树节点个数相等。
2、两棵树的以根节点的儿子为根子树对应同构。如下图,为两棵同构的有根树。
如下图,为两棵同构的有根树。
 

Input

一行两个整数n,m分别表示两棵有根树的大小。
以下n-1行描述第一棵树,每行两个数x,y表示x号节点是y号节点父亲。
以下m-1行描述第二棵树,每行两个数x,y表示x号节点是y号节点父亲。
数据保证两棵树的1号节点均为根。

Output

一行一个数,表示两棵树的相似度(删除后最大化的同构树的大小)。
 

Sample Input

3 3
1 2
1 3
1 2
2 3

Sample Output

2
【样例解释】
第一棵树可以保留1号节点和2号节点删除3号节点,也可以保留1号节点与3号节点删除2号节点,
第二棵树保留1号节点和2号节点删除3号节点。
剩下的树同构,树的节点个数均为2。

Data Constraint

对于30%的数据,1 ≤ n ≤10
对于60%的数据,1 ≤ n ≤ 100
对于100%的数据,1 ≤ n ≤ 1000数据保证两棵树上每个节点的度均不超过5。

 

题意:给定两棵树,任意删除边,求让它们同构的方案中保留最多节点的方案保留的节点数

同构的定义  1、两棵树节点个数相等。 2、两棵树的以根节点的儿子为根子树对应同构。如下图,为两棵同构的有根树。

题解:

这是一道比较裸的 DP 题,相信大家都看出来了。这题的难 点主要是状态的转移。

30%:直接上暴力。枚举两颗树中保留哪些节点,求最大的同构。

100%:树型 DP。设状态F[x][y]表示,第一棵树中x节点与第二棵树中的y节点作为根节点匹配的最大同构。例如,上图中所示的F[x][y] = 3。现在转移方程就很显然了: F[x][y]=Max{F[x的儿子][y的儿子]}+1 ; 这里需要枚举x的那些儿子与y的哪些儿子匹配,这部分时间复杂度O(5!) ; 最后答案就是F[1][1]. 总的时间复杂度为O(5!*n2

也就是说,在状态转移的时候,我们要让x的子节点与y的子节点匹配。在程序中具体的实现就是先dfs树1,枚举x的子节点,然后再处理树2,一个个枚举y的子节点,搜索改变匹配序列就好。

注意到度数最大就是5,所以说不需要担心时间问题。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=1000+15;
int n,m,x,y;
int f[maxn][maxn],vis[maxn];
vector <int> son1[maxn];
vector <int> son2[maxn];
inline int read()
{
    char ch=getchar();
    int s=0,f=1;
    while (!(ch>='0'&&ch<='9')) {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*f;
}
void dfs1(int k,int s)
{
    if (k>son1[x].size()) {f[x][y]=max(f[x][y],s);return;}//当点x的孩子访问完时,是时候返回值了
    dfs1(k+1,s);
    bool flag=true;
    for (int j=0;j<son2[y].size();j++)
    if (!vis[j])
    {
        vis[j]=1;
        flag=false;
        dfs1(k+1,s+f[son1[x][k-1]][son2[y][j]]);//加上子节点的最大同构值
        vis[j]=0;
    }
    if (flag) f[x][y]=max(f[x][y],s);
}
void dfs(int k)
{
    if (!son1[k].size())//当k节点没有孩子时
    {
        for (int i=1;i<=m;i++)
            f[k][i]=1;//只有k节点一个可以同构
        return;
    }
    for (int i=0;i<son1[k].size();i++)
    dfs(son1[k][i]);
    for (int i=1;i<=m;i++)
    {
        if (!son2[i].size()) f[k][i]=1;
        else 
        {
            x=k;y=i;
            dfs1(1,0);
            f[k][i]++;//最后算上k节点一个
        }
    }
}
int main()
{
//    freopen("frand.in","r",stdin);
//    freopen("frand.out","w",stdout);
    n=read();m=read();
    for (int i=1;i<n;i++)
    {
        int xx=read(),yy=read();
        son1[xx].push_back(yy);
    }
    for (int i=1;i<m;i++)
    {
        int xx=read(),yy=read();
        son2[xx].push_back(yy);
    }
    dfs(1);
    printf("%d\n",f[1][1]);
    return 0;
}

 

posted @ 2018-07-12 21:53  星星之火OIer  阅读(181)  评论(0编辑  收藏