最近在做些树形DP练练手

原题链接

大意就是给你一棵树,你可以断开任意数量的边,使得剩下的联通块大小乘积最大。

样例

8

1 2

1 3

2 4

2 5

3 6

3 7

6 8

输出

18

我首先想的是设\(f[i]\)表示以\(i\)为根的子树可获得的最大收益,但是会发现这样无法转移。考虑再加一维,\(f[i][j]\)表示以\(i\)的子树中,\(i\)所在的联通块大小为\(j\)的最大价值。然后我就傻了,想了半天也没想起来怎么转移,最后只好看了一眼题解。其实转移好简单的,貌似是个树上背包?考虑在\(dfs\)的过程中进行\(DP\),每当访问完一个点\(i\)的子结点时,累加一下\(sz[i]\),就枚举\(j\),并且用当前子结点的\(DP\)值来更新\(f[i][j]\)。转移方程大概会长成下面这个样子:

$f[i][j]=max(f[i][j],f[i][k]*f[v][j-k])$
(理解的话,就是把之前的大小为$k$的联通块和在当前子树中大小为$j-k$的联通块拼起来)
同时,我们特别定义$f[i][0]$表示以$i$为根的子树可获得的价值,则他的转移方程比较特殊:
$f[i][0]=max(f[i][0],f[i][j]*j)$
如果到这里这道题就结束的话,代码会长成下面这样:
```cpp
#include

using namespace std;

define N 700

define ll long long

int n, eid, sz[N+5], head[N+5];

ll f[N+5][N+5];

struct Edge {

int next, to;

}e[2*N+5];

void addEdge(int u, int v) {

e[++eid].next = head[u];

e[eid].to = v;

head[u] = eid;

}

void dp(int u, int fa) {

sz[u] = 1, f[u][0] = f[u][1] = 1;

for(int i = head[u]; i; i = e[i].next) {

int v = e[i].to;

if(v == fa) continue;

dp(v, u);

sz[u] += sz[v];

for(int j = sz[u]; j >= 1; --j) { //枚举i所在的联通块大小

for(int k = min(j, sz[u]-sz[v]); k >= max(1, j-sz[v]); --k) { //枚举子树根结点所在联通块大小

f[u][j] = max(f[u][j], f[u][k]f[v][j-k]);

}

}

}

for(int i = 1; i <= sz[u]; ++i) f[u][0] = max(f[u][0], f[u][i]
i);

}

int main() {

cin >> n;

for(int i = 1, x, y; i <= n-1; ++i) cin >> x >> y, addEdge(x, y), addEdge(y, x);

dp(1, 0);

cout << f[1][0] << endl;

return 0;

}

但是一交上去只有30$pts$,一看讨论区,发现还要用高精度!于是粘了个板子上去,然后就开心的$MLE$了 ̄▽ ̄。最后把$int$换成$short$就对了,无语。
粘一下$AC$代码
```cpp
#include <cstdio>
#include <iostream>
#include <cstring> using namespace std; #define N 700 int n, eid;
short sz[N+5], head[N+5]; struct Edge {
int next, to;
}e[2*N+5]; struct bign{ //高精类模板,网上找的
static const int maxn = 120;
short d[maxn+5];
short len;
void clean() { while(len > 1 && !d[len-1]) len--; }
bign() { memset(d, 0, sizeof(d)); len = 1; }
bign(int num) { *this = num; }
bign(char* num) { *this = num; }
bign operator = (const char* num) {
memset(d, 0, sizeof(d)); len = strlen(num);
for(int i = 0; i < len; i++) d[i] = num[len-1-i] - '0';
clean();
return *this;
}
bign operator = (int num){
char s[20]; sprintf(s, "%d", num);
*this = s;
return *this;
}
bign operator + (const bign& b) {
bign c = *this; int i;
for(i = 0; i < b.len; i++) {
c.d[i] += b.d[i];
if (c.d[i] > 9) c.d[i] %= 10, c.d[i+1]++;
}
while (c.d[i] > 9) c.d[i++] %= 10, c.d[i]++;
c.len = max(len, b.len);
if (c.d[i] && c.len <= i) c.len = i+1;
return c;
}
bign operator - (const bign& b) {
bign c = *this; int i;
for(i = 0; i < b.len; i++) {
c.d[i] -= b.d[i];
if (c.d[i] < 0) c.d[i] += 10, c.d[i+1]--;
}
while (c.d[i] < 0) c.d[i++] += 10, c.d[i]--;
c.clean();
return c;
}
bign operator * (const bign& b) const {
int i, j; bign c; c.len = len + b.len;
for(j = 0; j < b.len; j++)
for(i = 0; i < len; i++)
c.d[i+j] += d[i]*b.d[j];
for(i = 0; i < c.len-1; i++) c.d[i+1] += c.d[i]/10, c.d[i] %= 10;
c.clean();
return c;
}
bign operator / (const bign& b) {
int i, j;
bign c = *this, a = 0;
for(i = len - 1; i >= 0; i--) {
a = a*10 + d[i];
for (j = 0; j < 10; j++)
if (a < b*(j+1)) break;
c.d[i] = j;
a = a - b*j;
}
c.clean();
return c;
}
bign operator % (const bign& b) {
int i, j;
bign a = 0;
for(i = len - 1; i >= 0; i--) {
a = a*10+d[i];
for(j = 0; j < 10; j++) if (a < b*(j+1)) break;
a = a-b*j;
}
return a;
}
bign operator += (const bign& b) {
*this = *this+b;
return *this;
}
bool operator <(const bign& b) const {
if(len != b.len) return len < b.len;
for(int i = len-1; i >= 0; i--)
if(d[i] != b.d[i]) return d[i] < b.d[i];
return false;
}
bool operator >(const bign& b) const { return b < *this; }
bool operator <= (const bign& b) const { return !(b < *this); }
bool operator >= (const bign& b) const { return !(*this < b); }
bool operator != (const bign& b) const { return b < *this || *this < b; }
bool operator == (const bign& b) const { return !(b < *this) && !(b > *this); }
string str() const {
char s[maxn] = {};
for(int i = 0; i < len; i++) s[len-1-i] = d[i]+'0';
return s;
}
}f[N+5][N+5]; istream& operator >> (istream& in, bign& x) {
string s;
in >> s;
x = s.c_str();
return in;
} ostream& operator << (ostream& out, const bign& x) {
out << x.str();
return out;
} void addEdge(int u, int v) {
e[++eid].next = head[u];
e[eid].to = v;
head[u] = eid;
} void dp(int u, int fa) {
sz[u] = 1, f[u][0] = f[u][1] = 1;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v == fa) continue;
dp(v, u);
sz[u] += sz[v];
for(int j = sz[u]; j >= 1; --j) {
for(int k = min(j, sz[u]-sz[v]); k >= max(1, j-sz[v]); --k) {
f[u][j] = max(f[u][j], f[u][k]*f[v][j-k]);
}
}
}
for(int i = 1; i <= sz[u]; ++i) f[u][0] = max(f[u][0], f[u][i]*i);
} int main() {
cin >> n;
for(int i = 1, x, y; i <= n-1; ++i) cin >> x >> y, addEdge(x, y), addEdge(y, x);
dp(1, 0);
cout << f[1][0] << endl;
return 0;
}

洛谷 P1411 树的更多相关文章

  1. 洛谷 P1411 树 (树形dp)

    大意: 给定树, 求删除一些边, 使得连通块大小的乘积最大 设$dp_{i,j}$表示只考虑点$i$的子树, $i$所在连通块大小为$j$的最大值. 转移的时候不计算$i$所在连通块的贡献, 留到最后 ...

  2. 【算法学习】【洛谷】树链剖分 & P3384 【模板】树链剖分 P2146 软件包管理器

    刚学的好玩算法,AC2题,非常开心. 其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用. 更重要的是我很好调试,两题都是几乎一遍过的. 介绍树链剖分前,先确保已经学会以下基本技巧: ...

  3. 洛谷P3384 树链剖分

    如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x ...

  4. 洛谷 P3384 树链剖分(模板题)

    题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式 ...

  5. 洛谷P1268 树的重量

    P1268 树的重量 85通过 141提交 题目提供者该用户不存在 标签树形结构 难度提高+/省选- 提交该题 讨论 题解 记录 最新讨论 有这种情况吗!!!! 题意似乎有问题 题目描述 树可以用来表 ...

  6. 【树链剖分】洛谷P3379 树链剖分求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  7. 【树链剖分】洛谷P3384树剖模板

    题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式 ...

  8. 洛谷P3248 树 [HNOI2016] 主席树+倍增+分治

    正解:主席树+倍增+分治 解题报告: 传送门! 首先看到这题会想到之前考过的这题 但是那题其实简单一些,,,因为那题只要用个分治+预处理就好,只是有点儿思维难度而已 这题就不一样,因为它说了是按照原树 ...

  9. 洛谷P3368 树状数组2 树状数组+差分

    正解:树状数组+差分 解题报告: 戳我! 不得不说灵巧真滴是越来越弱了...连模板题都要放上来了QAQ 因为今天考试的T3正解要用到树状数组这才惊觉树状数组掌握得太太太太差了...之前一直靠线段树续着 ...

随机推荐

  1. nodejs 使用 js 模块

    nodejs 使用 js 模块 Intro 最近需要用 nodejs 做一个爬虫,Google 有一个 Puppeteer 的项目,可以用它来做爬虫,有关 Puppeteer 的介绍网上也有很多,在这 ...

  2. Angualr学习笔记

    0.安装即环境初始化 下载node至windows,点击安装,所有环境变量直接OK: linux下载tar后,解压,在/etc/profile的path路径下增加node执行路径: export PA ...

  3. 尝试Java,从入门到Kotlin(上)

    之前一直使用C#开发,最近由于眼馋Java生态环境,并借着工作服务化改造的契机,直接将新项目的开发都转到Java上去.积攒些Java开发经验,应该对.NET开发也会有所启发和益处. 从理论上说,Jav ...

  4. 【记录】Xmind8 Pro 激活

    摘要 XMind 是一个全功能的思维导图和头脑风暴软件,为激发灵感和创意而生.作为一款有效提升工作和生活效率的生产力工具,受到全球百千万用户的青睐. [有能力请支持正版]     在xmin下载xmi ...

  5. 用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列。

    数据库中有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列. 方法一: select (case when a>b then a el ...

  6. 如何设置Oracle数据库客户端字符集以及系统中的NLS_LANG环境变量

    概述: 本地化是系统或软件运行的语言和文化环境.设置NLS_LANG环境参数是规定Oracle数据库软件本地化行为最简单的方式. NLS_LANG参数不但指定了客户端应用程序和Oracle数据库所使用 ...

  7. md5sum的使用

    通过md5sum可以对文件做哈希校验,用来验证文件完整性. 批量生成校验值 $ find . -iname "*.mp4" -exec md5sum -t {} \; >/t ...

  8. SQLServer之创建DML AFTER INSERT触发器

    DML AFTER INSERT触发器创建原理 触发器触发时,系统自动在内存中创建deleted表或inserted表,内存中创建的表只读,不允许修改,触发器执行完成后,自动删除. insert触发器 ...

  9. 英语词性系列-B02-动词

    诗Poem 要求:背诵这首诗,翻译现代文,根据现代文用简单的英文翻译. 动词直观体会 动词 动词 动词 动词 动词 sell卖 buy买 beat击打 look看 dance跳舞 sing唱歌 spe ...

  10. R语言学习——处理数据对象的实用函数

    length(object) # 显示对象中元素/成分的数量 dim(boject) # 显示某个对象的维度 str(object) # 显示某个对象的结构 class(object) # 显示某个对 ...