传送门

题目大意:

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

题目分析:

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

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

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

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

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

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

code

#include<bits/stdc++.h>
using namespace std;
#define maxn 100050
#define limit (1<<21)
const int mod = 1e9 + 7;
namespace IO{
inline int read(){
int i = 0, f = 1; char ch = getchar();
for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
if(ch == '-') f = -1, ch = getchar();
for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0');
return i * f;
}
inline void wr(int x){
if(x < 0) x = -x, putchar('-');
if(x > 9) wr(x / 10);
putchar(x % 10 + '0');
}
}using namespace IO; int n;
int ecnt, adj[maxn], go[maxn*2], nxt[maxn*2], fa[maxn][25];
int dep[maxn], sze[maxn], num[maxn], leaf[maxn], tot;
int vir[maxn], virCnt, vecnt, vadj[maxn], vgo[maxn], vnxt[maxn], par[maxn], rt;
int dfn[maxn], clk, sum;
int f[limit];
double g[limit]; inline void addEdge(int u, int v){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
} inline void addvEdge(int u, int v){
vnxt[++vecnt] = vadj[u], vadj[u] = vecnt, vgo[vecnt] = v;
} inline void pre(int u, int f){
// cout<<u<<"->";
dep[u] = dep[f] + 1;
dfn[u] = ++clk;
fa[u][0] = f;
sze[u] = 1;
for(int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
int cnt = 0;
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
pre(v, u);
sze[u] += sze[v];
cnt++;
}
if(u == 1) return;
if(cnt == 0) leaf[tot++] = u, vir[++virCnt] = u;
else if(cnt >= 2) vir[++virCnt] = u;
} inline int getLca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
int delta = dep[u] - dep[v];
for(int i = 20; i >= 0; i--) if(delta & (1 << i)) u = fa[u][i];
if(u == v) return u;
for(int i = 20; i >= 0; i--)
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][0];
} inline bool cmp(int u, int v){
return dfn[u] < dfn[v];
} inline int buildVir(){
static int stk[maxn], top;
top = 0;
sort(vir + 1, vir + virCnt + 1, cmp);
int oriSze = virCnt;
for(int i = 1; i <= oriSze; i++){
int u = vir[i];
if(!top){
stk[++top] = u;
par[u] = 0;
continue;
}
int lca = getLca(u, stk[top]);
// cout<<u<<" "<<stk[top]<<" "<<lca<<endl;
while(dep[lca] < dep[stk[top]]){
if(dep[stk[top - 1]] < dep[lca]) par[stk[top]] = lca;
top--;
}
if(lca != stk[top]){
vir[++virCnt] = lca;
stk[++top] = lca;
par[lca] = stk[top];
}
par[u] = lca;
stk[++top] = u;
}
sort(vir + 1, vir + virCnt + 1, cmp);
for(int i = 1; i <= virCnt; i++)
if(par[vir[i]]) addvEdge(par[vir[i]], vir[i]);
return vir[1];
} inline void number(int u){
if(!vadj[u]){
sum += num[u];
return;
}
num[u] = 0;
for(int e = vadj[u]; e; e = vnxt[e]){
int v = vgo[e];
number(v);
if(num[v] == sze[v]){ //该子树已经全部染完
num[u] += num[v] + dep[v] - dep[u] - 1;
sum += dep[v] - dep[u] - 1; //更新已经染到的编号
}
}
if(num[u] == sze[u] - 1){ //该根的子树已经全部染完
num[u]++, sum++;
}
} inline void print(int t){
cout<<t<<"->";
for(int e = vadj[t]; e; e = nxt[e]){
int v = vgo[e];
print(v);
}
} int main(){
n = read();
for(int i = 1; i < n; i++){
int x = read(), y = read();
addEdge(x, y), addEdge(y, x);
}
vir[virCnt = 1] = 1;
pre(1, 0);
rt = buildVir();
for(int i = 0; i < tot; i++) f[1 << i] = 1, g[1 << i] = 1.0;
int limi = (1 << tot);
for(int i = 1; i < limi; i++){
for(int j = 0; j < tot; j++){
if(i & (1 << j)) num[leaf[j]] = 1;
else num[leaf[j]] = 0;
}
sum = 0;
number(rt); //得到下一个叶子节点的编号
double ret = g[i] * (sum + 1); //用double来做中间的比较,避免高精度
int ans = 1ll * f[i] * (sum + 1) % mod;
for(int j = 0; j < tot; j++){
if(((i & (1 << j)) == 0) && g[i | (1 << j)] < ret){
g[i | (1 << j)] = ret;
f[i | (1 << j)] = ans;
}
}
}
wr(f[limi - 1]);
return 0;
}

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. 【干货】前端开发者最常用的六款IDE

    一.Visual Studio Code 下载地址:https://code.visualstudio.com/ 功能介绍: 微软在2015年4月30日Build 开发者大会上正式宣布了 Visual ...

  2. C# 映射

    public class Myclass1 { private int m_Count = 100; public string love{get;set;} public int Count { g ...

  3. JS概述

             从Asp.NET跨越到JavaScript.这既是一个新的领域也是一个非常熟悉的地方,新是由于不知道什么是JavaScript,首先来了解一下什么是JavaScript.       ...

  4. android--显式跳转和隐式跳转的差别使用方法

    #创建第二个activity * 新创建的activity.必须在清单文件里做配置,否则系统找不到,在显示时会直接报错 <activity android:name="com.ithe ...

  5. 【万里征程——Windows App开发】DatePickerFlyout、TimePickerFlyout的使用

    已经有挺长时间没有更新这个专栏了,只是刚才有网友私信问我一个问题如今就火速更新上一篇~ 这一篇解说在WP上DataPickerFlyout和TimePickerFlyout的使用.但它们仅仅能在WP上 ...

  6. Cscope how to support java and c++

    Cscope 首先在文件夹下建立cscope索引文件 find -name '*.c' > cscope.file cscope -Rbkq 这个命令会生成三个文件:cscope.out, cs ...

  7. Altium Designer中距离的测量

    Ctrl+M 清除测量标签:点击右下角的清除按键

  8. UVA 11584 - Partitioning by Palindromes DP

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  9. [Vue] Load components when needed with Vue async components

    In large applications, dividing the application into smaller chunks is often times necessary. In thi ...

  10. 函数的引用透明性(referential transparency)

    1. 基础 初学程序设计时,比较容易混淆的两个概念是数学函数(math function)和程序中使用的函数. 在数学函数中 y=f(x),一个输入值有固定的输出值.例如,无论计算多少次,sinπ 的 ...