最近在做些树形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. python(day17)二分查找

    l = [1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31] def find(l ,aim ,start = 0,end = None): end = len(l ...

  2. Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...

  3. Linux & Windows 环境下 RabbitMQ 安装与基本配置

    索引: 目录索引 参看代码 GitHub: rabbitmq.txt 一.Linux (DeepinOS) 环境 .安装: sudo apt install rabbitmq-server .进入目录 ...

  4. JavaCC的TokenManager和Parser

    TokenManager不会感知Parser的存在,这意味着TokenManager会尽量匹配足够长的终结符,而不是依据Parser的语法规则. 当被解析的文本为" @@ "时,T ...

  5. 20180903 - Python Pip 工具下载whl包与离线安装

    20180903 - Python Pip 工具下载whl包与离线安装 1. 我的Blog 博客园 https://www.cnblogs.com/piggybaba 个人网站 http://pigg ...

  6. SQLServer之修改索引

    使用SSMS数据库管理工具修改索引 使用表设计器修改索引 表设计器可以修改任何类型的索引,修改索引的步骤相同,本示例为修改唯一非聚集索引. 1.连接数据库,选择数据库,选择数据表->右键点击表- ...

  7. python 结巴分词学习

    结巴分词(自然语言处理之中文分词器) jieba分词算法使用了基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能生成词情况所构成的有向无环图(DAG), 再采用了动态规划查找最大概率路径,找出基于 ...

  8. 从开始到头皮炸裂的python第5天

    头皮炸裂的一天从学到一个新的数据类型开始,这个数据类型的新成员叫做字典,基本的格式为data={键:值,键:值},info.keys()表示所有的键,info.values()表示所有的值,info. ...

  9. R语言学习——数据框

    > #数据框可以包含不同模式(数值型.字符型.逻辑型等)的数据,是R中最常处理的数据结构.数据框可以通过函数data.frame()创建:mydata<-data.frame(coll,c ...

  10. day6-基础函数的学习(一)

    今日份目录 1.函数的定义 2.函数的返回值 3.函数的参数 4.函数的局部变量与全局变量 5.名称空间与作用域 6.global与nonlocal 7.高阶函数 继续今日份总结!这个总结也晚了,哎, ...