刷题总结——树有几多愁(51nod1673 虚树+状压dp+贪心)

题目:

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

题解

  挺神奇复杂的一道题···

  首先是这道题的基本策略··容易想到贪心··我们尽量将小的数放在越靠近叶节点的地方···因为深度越小的点对所有叶节点的影响肯定是越大的····所以我们考虑每次先将一个节点子树填完后再填它本身··且它本身的编号一定是与子树中最大编号连续的···

  另外由于只有20个叶节点··可以想到树上一定会有很多的链····对于链结合上面的策略我们可以相当链的编号一定是从链最下面的节点到上面严格递增的····因此我们可以将原树中的链全部省略掉···新建一个虚树··新的边为原来链的长度,由只有20个叶节点可以推出新的树的节点数不会多于100个··大大减少复杂度··

  最后由20个叶节点可以想到状压dp····这是本题中最为复杂的地方···的

  我们用f[i]表示我们选取了i状态叶节点下的乘积的最小值···首先由i状态我们可以确定有哪些节点的所在子树的叶节点是全部取到了的···由此该节点u所在子树到它在新树中的父亲节点的编号肯定是确定的(由基本策略可以推出编号肯定是已经填到了1——size[u]+len[u],size[u]为u所在子树大小,len[u]为它到它在新树中的父亲节点的边的长度)

  设上面的范围为(1——t),那么接下来要填的叶子节点的编号肯定是t+1,此时我们只需枚举接下来要填的叶子节点是哪一个···然后用f[i]*(t+1)的值去更新f[i|(x)]即可,其中x为我们枚举的那一个叶子····

   另外注意本题是要取模的··但为了在dp时比较大小我们需要准备两个dp数组··一个用于记录正确的取了模的答案··一个用于比较··比较的那个数组可以用log或者用double来比较(double的范围是很大的····1.7*10(308))

代码:

  

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
int n,first[N],go[N*2],next[N*2],tot,cntlf,lf[25],size[105],cnt,lim,got[105],len[105];
long long dp[1<<20];
double f[1<<20];
bool islf[N],del[N];
vector<int>g[105];
inline int R()
{
  char c;int f=0;
  for(c=getchar();c<'0'||c>'9';c=getchar());
  for(;c<='9'&&c>='0';c=getchar())  f=(f<<3)+(f<<1)+c-'0';
  return f;
}
inline void comb(int a,int b)
{
  next[++tot]=first[a],first[a]=tot,go[tot]=b;
  next[++tot]=first[b],first[b]=tot,go[tot]=a;
}
inline void dfs1(int u,int fa)
{
  int sum=0;
  for(int e=first[u];e;e=next[e])
  {
    int v=go[e];if(v==fa)  continue;
    sum++;dfs1(v,u); 
  }
  if(!sum)  islf[u]=true;
  else if(sum==1)  del[u]=true;
}
inline void dfs2(int u,int fa,int tempfa,int templen)
{
  templen++;
  if(!del[u])
  {
    len[++cnt]=templen;templen=0;
    if(islf[u])  lf[++cntlf]=cnt;
    if(tempfa)  g[tempfa].push_back(cnt);
    tempfa=cnt;
  }
  for(int e=first[u];e;e=next[e])
  {
    int v=go[e];if(v==fa)  continue;
    dfs2(v,u,tempfa,templen);
  }
}
int main()
{
  //freopen("a.in","r",stdin);
  n=R();int a,b;    
  for(int i=1;i<n;i++)  a=R(),b=R(),comb(a,b);
  dfs1(1,0);dfs2(1,0,0,0);
  for(int i=1;i<=cntlf;i++)  size[lf[i]]=1;
  for(int i=cnt;i>=1;i--)    
    for(int j=0;j<g[i].size();j++)  size[i]+=size[g[i][j]];
  lim=(1<<cntlf);dp[0]=f[0]=1;
  for(int i=0;i<lim;i++)
  {
    int num=1;
    memset(got,0,sizeof(got));   
    for(int j=1;j<=cntlf;j++)
      if(i&(1<<(j-1)))  got[lf[j]]=1;
    for(int j=cnt;j>=1;j--)
    {  
      for(int k=0;k<g[j].size();k++)  got[j]+=got[g[j][k]];
      if(got[j]==size[j])  num+=len[j];
    }
    double temp=f[i]*num;
    for(int j=1;j<=cntlf;j++)
      if(!(i&(1<<(j-1)))&&temp>f[i|(1<<(j-1))])  
        f[i|(1<<(j-1))]=temp,dp[i|(1<<(j-1))]=(long long)dp[i]*num%mod;
  }
  cout<<dp[lim-1]<<endl; 
  return 0;
}

 







posted @ 2017-10-23 19:39  AseanA  阅读(529)  评论(1编辑  收藏  举报