51nod1673 树有几多愁 【虚树+树形dp+状压dp】

题目描述

lyk有一棵树,它想给这棵树重标号。
重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号。
这棵树的烦恼值为所有叶子节点的值的乘积。
lyk想让这棵树的烦恼值最大,你只需输出最大烦恼值对1e9+7取模后的值就可以了。
注意一开始1号节点为根,重标号后这个节点仍然为根。

数据保证叶子节点个数<=20。

例如样例中,将1,2,3,4,5重标号为4,3,1,5,2,此时原来编号为4,5的两个叶子节点的值为3与1,这棵树的烦恼值为3。不存在其它更优解。

Input

第一行一个数n(1<=n<=100000)。
接下来n-1行,每行两个数ai,bi(1<=ai,bi<=n),表示存在一条边连接这两个点。

Output

一行表示答案

Input示例

5
1 2
2 4
2 3
3 5

Output示例

3

解题思路:

非常好的一道树形dp+状压dp+虚树。

首先我们发现叶子节点一定比父节点要小,因为你要让小得数尽量往下放(这样影响最小),并且会发现如果子树是一条链的话,那么那么一定是从小往上排着放,因为这条链上的最小值已经确定并且对其他子树没有影响,所以要把前几小得数都放在这条链上。那么我们发现这条链只与链的长度有关,与具体的形态无关。

所以我们可以建虚树,所谓虚树就是我把原树中没用的点或边删掉,重新建一颗有用的树,这是一种思想,只保留有用 的边和点,像krustra重构树也是这样。

那么问题就变成了往树上每条单链上都放一段连续的区间的数,父节点权值要比子链上的大,最后使每段叶子节点所在最小权值之积最大。 如样例中就相当于(5,3)这条链上放1、2,(4)上放3,(2,1)上放4,5,最后答案就是1*3=3;可以发现叶子节点所在链上放的都是数值最小的区间。

那按怎样的顺序放置最小的几个区间才能是该积最大呢?注意到数据范围叶子节点数才20,像这么小的数(<=20),一定要往状压dp上想。所以我们用二进制表示每个叶节点所在链选的情况及顺序了,有点像hdu的Doing homework一题。到此题目考虑完毕,基本的算法已经想出来了,下面就是具体实现了。

首先是虚树怎么建,前面已经说了一条链的情况是没有什么用的,所以可以删掉。那么我们只需要保存叶子节点和子节点大于1的节点。

建完新树之后,我们要算一下每个节点下的叶子个数(size),还要记一下实际选了哪些叶子(got数组),如果实际的个数=size,那么我们就可以把当前算到的编号加上这个点以上省略点的个数。状态转移就是直接枚举其他没选的叶子是不是选就行了。
还有一处细节,由于取模之后没办法比较大小,所以要用一个double类型的数组存大小(long long 存不下,而double可转为科学计数法,也可用log来比,但精度其实是一样的)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=100005,p=1e9+7;
int n;
int tot,first[N],nxt[N<<1],to[N<<1];
bool isleaf[N],del[N];
int cnt,leafnum,leaf[100],size[100],len[100],got[100];
vector<int>edge[100];
double f[1<<20];
int dp[1<<20];

void add(int x,int y)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y;
}

void dfs(int u,int fa)
{
    int sonnum=0;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(v!=fa)
        {
            sonnum++;
            dfs(v,u);
        }
    }
    if(!sonnum)isleaf[u]=true;
    if(sonnum==1)del[u]=true;
}

void dfs1(int u,int fa,int newfa,int tmplen)
{
    tmplen++;
    if(!del[u])
    {
        cnt++;
        len[cnt]=tmplen,tmplen=0;
        if(isleaf[u])leaf[++leafnum]=cnt;
        if(newfa)edge[newfa].push_back(cnt);
        newfa=cnt;
    }
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(v!=fa)dfs1(v,u,newfa,tmplen);
    }
}

int main()
{
    //freopen("tree.in","r",stdin);
    //freopen("tree.out","w",stdout);
    int x,y;
    n=getint();
    for(int i=1;i<n;i++)
    {
        x=getint(),y=getint();
        add(x,y),add(y,x);
    }
    dfs(1,0);
    dfs1(1,0,0,0);

    int mx=1<<leafnum;
    for(int i=1;i<=leafnum;i++)size[leaf[i]]=1;
    for(int u=cnt;u;u--)
        for(int i=0;i<edge[u].size();i++)
            size[u]+=size[edge[u][i]];

    f[0]=dp[0]=1;
    for(int now=0;now<mx;now++)
    {
        memset(got,0,sizeof(got));
        for(int i=1;i<=leafnum;i++)
            if(now&(1<<i-1))got[leaf[i]]=1;
        int num=1;
        for(int u=cnt;u;u--)
        {
            for(int i=0;i<edge[u].size();i++)
                got[u]+=got[edge[u][i]];
            if(got[u]==size[u])num+=len[u];
        }
        double tmp=f[now]*(double)num;
        for(int i=1;i<=leafnum;i++)
            if(!(now&(1<<i-1))&&tmp>f[now|(1<<i-1)])
                f[now|(1<<i-1)]=tmp,dp[now|(1<<i-1)]=1ll*dp[now]*num%p;     
    }
    cout<<dp[mx-1];
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值