2018湖北省大学程序设计竞赛 D. Who killed Cock Robin

链接:https://www.nowcoder.com/acm/contest/104/C
Who killed Cock Robin?
I, said the Sparrow, With my bow and arrow,I killed Cock Robin.
Who saw him die?
I, said the Fly.With my little eye,I saw him die.
Who caught his blood?
I, said the Fish,With my little dish,I caught his blood.
Who’ll make his shroud?
I, said the Beetle,With my thread and needle,I’ll make the shroud.
………
All the birds of the air
Fell a-sighing and a-sobbing.
When they heard the bell toll.
For poor Cock Robin.
March 26, 2018

Sparrows are a kind of gregarious animals,sometimes the relationship between them can be represented by a tree.
The Sparrow is for trial, at next bird assizes,we should select a connected subgraph from the whole tree of sparrows as trial objects.
Because the relationship between sparrows is too complex, so we want to leave this problem to you. And your task is to calculate how many different ways can we select a connected subgraph from the whole tree.
题意就是求一棵树中有多少个联通子树(不是子链),应该是比较基本的树形dp吧,不过树形dp还没做过==

dp[i]代表以i为起点的子树有多少个。
当时被卡住的点是状态转移方程的写法,树形dp一般都是由子节点向根节点更新,
这个题也是这样的,因为dp[i]是以i为起点的子树的个数,所以根节点只能由子节点更新过来,否则就不是联通子树了。。。

2018湖北省大学程序设计竞赛 D. Who killed Cock Robin_第1张图片
如上图,画的比较抽象,now代表根节点,next代表子节点,3个dp[next]分别已经被计算出来,分别为x1,x2,x3,现在考虑如何从3个更新now。

当时一开始的想法是dp[now]=x1+x2+x3+x1*x2+x1*x3+x2*x3+x1*x2*x3+1,这个转移方程应该也是没问题的,也很好理解,比如其中的一项x1*x2,这一项代表从第一个子节点中选一颗子树,再从第二个子节点选一个子树,再加上now根节点,然后这样就能对dp[now]贡献一棵新的子树,然后用乘法原理,就是x1*x2,其他项类似。
但是这样做问题在于这个求n个数的全排列,指数级别的时间复杂度,肯定无法承受,所以这种想法是没办法解决这个问题的。

正确的思路是首先将dp[now]初始化为1,意思就是只有now一个节点也是以now为起点的一棵子树,然后通过第一个子节点更新,dp[now]=dp[now]+dp[now]*x1,意思很好理解,就是在第一个子节点x1棵子树中选择任何一个,再加上根节点就又能变成一个以now为起点的新子树,于是我们就更新完了第一个子节点。
接下来就是最为关键的时刻了==

2018湖北省大学程序设计竞赛 D. Who killed Cock Robin_第2张图片
我们把now和第一个子节点看成一个整体,记为now’,dp[now’]已经求得,因为第一个子节点已经更新过了,然后再更新第二个子节点,那么不就和刚才更新第一个节点一样了吗!!!dp[now’]=dp[now’]+dp[now’]*x2,这就是更新完第二个子节点后的状态。

然后就把now和第一个子节点第二个子节点看成一个整体,记为now”,这样一个一个子节点的更新就好了。。。。
代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define maxn 200010
#define ll long long
ll dp[maxn];
vector<int>vec[maxn];
const ll mod=1e7+7;
int n;
void dfs(int now)
{
    if(vec[now].size()==0)//子节点为0则只可能有一个子树就是它本身
    {
        dp[now]=1;
        return;
    }
    dp[now]=1;
    for(int i=0;iint v=vec[now][i];
        dfs(v);
        dp[now]+=(dp[now]*dp[v]);//上面解释的转移方程
        dp[now]%=mod;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;iint x,y;
        scanf("%d%d",&x,&y);
        if(x>y)
            swap(x,y);//保证根节点为1且都是向下深搜
        vec[x].push_back(y);
    }
    dfs(1);
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ans+=dp[i];
        ans%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:(树形dp)