51nod1673树有几多愁

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

update:数据保证叶子节点个数<=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重构树也是这样。
我们再接着看数据范围叶子节点数才20,像这么小的数(<=20),一定要往状压dp上想。到此题目考虑完毕,基本的算法已经想出来了,下面就是具体实现了。
     首先是虚树怎么建,前面已经说了一条链的情况是没有什么用的,所以可以删掉。那么我们只需要保存叶子节点和子节点大于1的节点。建完新树之后,我们要算一下每个节点下的叶子个数(size),还要记一下实际选了哪些叶子(got数组),如果实际的个数=有的个数,那么我们就可以把当前算到的编号加上这个点以上省略点的个数。状态转移就是直接枚举其他没选的叶子是不是选就行了。
还有一处细节,由于取模之后没办法比较大小,所以要用一个double类型的存大小,(long long 存不下)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
typedef long long ll;
using namespace std;
const int mod=1e9+7;
const int N=100010;
int pre[N*2],last[N],other[N*2],num;
bool gg[N],leaf[N];
int len[N],leaf_pos[210],cnt,leafnum,size[N],got[210];
vector<int >vec[210];
double f[(1<<20)+N];
int g[(1<<20)+N];
inline void add(int x,int y){
	num++;
	pre[num]=last[x];
	last[x]=num;
	other[num]=y;
}
void dfs(int x,int fa){
	int son=0;
	for(int i=last[x];i;i=pre[i]){
		int v=other[i];
		if(v!=fa){
			dfs(v,x);
			son++;
		}
	}
	leaf[x]=(son==0);
	if(son==1) gg[x]=1;
}
void dfs1(int x,int fa,int _fa,int tmplen){
	tmplen++;
	if(!gg[x]){
		len[++cnt]=tmplen;tmplen=0;//记录它上面有几个点被删了 
		if(leaf[x]) leaf_pos[++leafnum]=cnt;//记录叶子的个数和每个叶子的新的编号 
		if(_fa) vec[_fa].push_back(cnt);//建新图 
		_fa=cnt;
	}
	int v;
	for(int i=last[x];i;i=pre[i]) if((v=other[i])!=fa) dfs1(v,x,_fa,tmplen);
}
int main(){
	int n,x,y;
	scanf("%d",&n);
	for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	dfs(1,0);dfs1(1,0,0,0);
	int st=(1<<leafnum);
	for(int i=1;i<=leafnum;i++) size[leaf_pos[i]]++;
	
	int sz;
	for(int i=cnt;i>=1;i--){//按dfs序更新,保证先孩子再父亲 
		for(int j=0,sz=vec[i].size();j<sz;j++)
		size[i]+=size[vec[i][j]];
	}
    f[0]=g[0]=1;
	for(int i=0;i<st;i++){
		memset(got,0,sizeof(got));
		for(int j=0;j<leafnum;j++){
			if(i&(1<<j)) got[leaf_pos[j+1]]++; 
		}
		int now=1;
		for(int j=cnt;j>=1;j--){
			for(int k=0,sz=vec[j].size();k<sz;k++)
			got[j]+=got[vec[j][k]];
			if(got[j]==size[j]) now+=len[j];
		}
		double tmp=f[i]*(double)now;
		for(int j=0;j<leafnum;j++){
			if(((1<<j)&i)==0&&tmp>f[i|(1<<j)]) 
			f[i|(1<<j)]=tmp,g[i|(1<<j)]=1ll*g[i]*now%mod;
		}
	}
	printf("%d\n",g[st-1]);
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值