传送门

题目大意:

给一颗重新编号,叶子节点的值定义为他到根节点编号的最小值,求所有叶子节点值的乘积的最大值。

题目分析:

为什么我觉得这道题最难的是贪心啊。。首先要想到

  1. 在一条链上,深度大的编号要小于深度小的编号(保证它影响的节点是最小的)
  2. 有了1过后,一颗子树的编号应该是以叶子节点为最小的连续整数,也就是说必须对一个节点的所有子树编完号才能对该节点编号。

这两点我已经想了很久了,接下来还有难关:

知道了叶子节点编号要最小,但是叶子节点的编号顺序会对答案产生巨大影响。注意到题目中保证叶子节点数\(\le 20\),-------->状压,用\(f[i]\)表示染完i这个状态的叶子所得到的最优答案(取模),由于中途转移时会产生巨大的中间量,为了避免使用高精度,再新建一个\(g[i]\)表示最优答案(未取模)。这样当枚举到某一状态时,计算出下一个叶子节点的编号应该是多少,并进行转移。

还没完,计算下一个叶子节点的编号需要对树进行一次遍历,由于n巨大,如果对原树进行遍历的话,总时间复杂度会达到\(O(2^{20}*n)\),这就远超出了范围。有前面得知一条链上的编号是连续的,那么就是说只要知道链的长度就可以知道编号的增量,也就是说我们只用保留叶节点、根节点、包含多颗子树的节点这些关键点,这不就是颗虚树吗?

建完虚树后,进行如上转移,即可得到答案。

code

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define maxn 100050
  4. #define limit (1<<21)
  5. const int mod = 1e9 + 7;
  6. namespace IO{
  7. inline int read(){
  8. int i = 0, f = 1; char ch = getchar();
  9. for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
  10. if(ch == '-') f = -1, ch = getchar();
  11. for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0');
  12. return i * f;
  13. }
  14. inline void wr(int x){
  15. if(x < 0) x = -x, putchar('-');
  16. if(x > 9) wr(x / 10);
  17. putchar(x % 10 + '0');
  18. }
  19. }using namespace IO;
  20. int n;
  21. int ecnt, adj[maxn], go[maxn*2], nxt[maxn*2], fa[maxn][25];
  22. int dep[maxn], sze[maxn], num[maxn], leaf[maxn], tot;
  23. int vir[maxn], virCnt, vecnt, vadj[maxn], vgo[maxn], vnxt[maxn], par[maxn], rt;
  24. int dfn[maxn], clk, sum;
  25. int f[limit];
  26. double g[limit];
  27. inline void addEdge(int u, int v){
  28. nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
  29. }
  30. inline void addvEdge(int u, int v){
  31. vnxt[++vecnt] = vadj[u], vadj[u] = vecnt, vgo[vecnt] = v;
  32. }
  33. inline void pre(int u, int f){
  34. // cout<<u<<"->";
  35. dep[u] = dep[f] + 1;
  36. dfn[u] = ++clk;
  37. fa[u][0] = f;
  38. sze[u] = 1;
  39. for(int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
  40. int cnt = 0;
  41. for(int e = adj[u]; e; e = nxt[e]){
  42. int v = go[e];
  43. if(v == f) continue;
  44. pre(v, u);
  45. sze[u] += sze[v];
  46. cnt++;
  47. }
  48. if(u == 1) return;
  49. if(cnt == 0) leaf[tot++] = u, vir[++virCnt] = u;
  50. else if(cnt >= 2) vir[++virCnt] = u;
  51. }
  52. inline int getLca(int u, int v){
  53. if(dep[u] < dep[v]) swap(u, v);
  54. int delta = dep[u] - dep[v];
  55. for(int i = 20; i >= 0; i--) if(delta & (1 << i)) u = fa[u][i];
  56. if(u == v) return u;
  57. for(int i = 20; i >= 0; i--)
  58. if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
  59. return fa[u][0];
  60. }
  61. inline bool cmp(int u, int v){
  62. return dfn[u] < dfn[v];
  63. }
  64. inline int buildVir(){
  65. static int stk[maxn], top;
  66. top = 0;
  67. sort(vir + 1, vir + virCnt + 1, cmp);
  68. int oriSze = virCnt;
  69. for(int i = 1; i <= oriSze; i++){
  70. int u = vir[i];
  71. if(!top){
  72. stk[++top] = u;
  73. par[u] = 0;
  74. continue;
  75. }
  76. int lca = getLca(u, stk[top]);
  77. // cout<<u<<" "<<stk[top]<<" "<<lca<<endl;
  78. while(dep[lca] < dep[stk[top]]){
  79. if(dep[stk[top - 1]] < dep[lca]) par[stk[top]] = lca;
  80. top--;
  81. }
  82. if(lca != stk[top]){
  83. vir[++virCnt] = lca;
  84. stk[++top] = lca;
  85. par[lca] = stk[top];
  86. }
  87. par[u] = lca;
  88. stk[++top] = u;
  89. }
  90. sort(vir + 1, vir + virCnt + 1, cmp);
  91. for(int i = 1; i <= virCnt; i++)
  92. if(par[vir[i]]) addvEdge(par[vir[i]], vir[i]);
  93. return vir[1];
  94. }
  95. inline void number(int u){
  96. if(!vadj[u]){
  97. sum += num[u];
  98. return;
  99. }
  100. num[u] = 0;
  101. for(int e = vadj[u]; e; e = vnxt[e]){
  102. int v = vgo[e];
  103. number(v);
  104. if(num[v] == sze[v]){ //该子树已经全部染完
  105. num[u] += num[v] + dep[v] - dep[u] - 1;
  106. sum += dep[v] - dep[u] - 1; //更新已经染到的编号
  107. }
  108. }
  109. if(num[u] == sze[u] - 1){ //该根的子树已经全部染完
  110. num[u]++, sum++;
  111. }
  112. }
  113. inline void print(int t){
  114. cout<<t<<"->";
  115. for(int e = vadj[t]; e; e = nxt[e]){
  116. int v = vgo[e];
  117. print(v);
  118. }
  119. }
  120. int main(){
  121. n = read();
  122. for(int i = 1; i < n; i++){
  123. int x = read(), y = read();
  124. addEdge(x, y), addEdge(y, x);
  125. }
  126. vir[virCnt = 1] = 1;
  127. pre(1, 0);
  128. rt = buildVir();
  129. for(int i = 0; i < tot; i++) f[1 << i] = 1, g[1 << i] = 1.0;
  130. int limi = (1 << tot);
  131. for(int i = 1; i < limi; i++){
  132. for(int j = 0; j < tot; j++){
  133. if(i & (1 << j)) num[leaf[j]] = 1;
  134. else num[leaf[j]] = 0;
  135. }
  136. sum = 0;
  137. number(rt); //得到下一个叶子节点的编号
  138. double ret = g[i] * (sum + 1); //用double来做中间的比较,避免高精度
  139. int ans = 1ll * f[i] * (sum + 1) % mod;
  140. for(int j = 0; j < tot; j++){
  141. if(((i & (1 << j)) == 0) && g[i | (1 << j)] < ret){
  142. g[i | (1 << j)] = ret;
  143. f[i | (1 << j)] = ans;
  144. }
  145. }
  146. }
  147. wr(f[limi - 1]);
  148. return 0;
  149. }

51nod1673 树有几多愁 - 贪心策略 + 虚树 + 状压dp的更多相关文章

  1. 【BZOJ2595_洛谷4294】[WC2008]游览计划(斯坦纳树_状压DP)

    上个月写的题qwq--突然想写篇博客 题目: 洛谷4294 分析: 斯坦纳树模板题. 简单来说,斯坦纳树问题就是给定一张有边权(或点权)的无向图,要求选若干条边使图中一些选定的点连通(可以经过其他点) ...

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

    题目: lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输 ...

  3. 51nod 1673 树有几多愁——虚树+状压DP

    题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1673 建一个虚树. 一种贪心的想法是把较小的值填到叶子上,这样一个小值限制到的 ...

  4. Codeforces 429C Guess the Tree(状压DP+贪心)

    吐槽:这道题真心坑...做了一整天,我太蒻了... 题意 构造一棵 $ n $ 个节点的树,要求满足以下条件: 每个非叶子节点至少包含2个儿子: 以节点 $ i $ 为根的子树中必须包含 $ c_i ...

  5. bzoj3717 [PA2014]Pakowanie 贪心+状压DP

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=3717 题解 这道题大概也就只能算常规的状压 DP 吧,但是这个状态和转移的设计还是不是很好想. ...

  6. 【62测试】【状压dp】【dfs序】【线段树】

    第一题: 给出一个长度不超过100只包含'B'和'R'的字符串,将其无限重复下去. 比如,BBRB则会形成 BBRBBBRBBBRB 现在给出一个区间[l,r]询问该区间内有多少个字符'B'(区间下标 ...

  7. bzoj 4006 [JLOI2015]管道连接(斯坦纳树+状压DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4006 [题意] 给定n点m边的图,连接边(u,v)需要花费w,问满足使k个点中同颜色的 ...

  8. 2018.08.29 NOIP模拟 movie(状压dp/随机化贪心)

    [描述] 小石头喜欢看电影,选择有 N 部电影可供选择,每一部电影会在一天的不同时段播 放.他希望连续看 L 分钟的电影.因为电影院是他家开的,所以他可以在一部电影播放过程中任何时间进入或退出,当然他 ...

  9. bzoj1402 Ticket to Ride 斯坦纳树 + 状压dp

    给定\(n\)个点,\(m\)条边的带权无向图 选出一些边,使得\(4\)对点之间可达,询问权值最小为多少 \(n \leqslant 30, m \leqslant 1000\) 首先看数据范围,\ ...

随机推荐

  1. python一切皆对象的理解

    min_error=pls(x_train, x_test, y_train, y_test) #这里我之前写的是error,但是前面有一个定义的error函数.所以导致出现了警告. 可能是因为pyt ...

  2. 微信支付v2开发(1) 微信支付URL配置

    本文介绍微信支付申请时如何设置授权目录及URL. 在申请微信支付时,第一项就会碰到下图的配置. 下面就对这一设置进行讲解! 一.选择支付类型 目前有两种支付类型 JS API网页支付 Native原生 ...

  3. Android 使用JSON格式与服务器交互 中文乱码问题解决

    当前是在开发Android 程序时,客户端与服务器端采用JSON传送数据,发现中文乱码问题.不过这个问题的解决办法应该对所有java语言开发的项目都使用. 解决方法是: 1.客户端发送数据之间加上: ...

  4. 常用加密算法的Java实现总结(二)

    常用加密算法的Java实现总结(二) ——对称加密算法DES.3DES和AES 摘自:http://www.blogjava.net/amigoxie/archive/2014/07/06/41550 ...

  5. 【Codeforces Round #301 (Div. 2) C】 Ice Cave

    [链接] 我是链接,点我呀:) [题意] 给你一个n*m的地图. 每个地图为0的时候可以安全走过,且走过后变成1. (一定要离开之后才会变成1) 而为1的则走过之后会掉入下一层. 你一开始在初始位置( ...

  6. iOS开发- iOS7显示偏差(UITableView下移)解决的方法

    之前碰到过一个问题. 就是利用storyboard拖动出来的控件, 在iOS7上跑老是莫名的下移. 比方这样(红色区域为多余的) 解决的方法: iOS7在Conttoller中新增了这个属性: aut ...

  7. js进阶 13-1 jquery动画中的显示隐藏函数有哪些

    js进阶 13-1 jquery动画中的显示隐藏函数有哪些 一.总结 一句话总结:show(),hide(),toggle(),这三个. 1.jquery动画中显示隐藏效果函数有哪些? show()h ...

  8. .dmp文件导出使用示例

    exp导出的几种用例,先睹为快: 1 将数据库SampleDB完全导出,用户名system 密码manager 导出到E:/SampleDB.dmp中 exp system/manager@TestD ...

  9. XML Parser Errors See Details for more Information XML Parser Error on line 1: Document root ele

    1.错误描写叙述 XML Parser Errors See Details for more Information XML Parser Error on line 1: Document roo ...

  10. percona-toolkit源码编译安装

    安装依赖软件yum install perl-ExtUtils-CBuilder perl-ExtUtils-MakeMakeryum install  perl-Time-HiRes perl-DB ...