树形dp的进阶 (一)
①树的重心的性质的运用
②缩点以后寻找规律 树的直径!
③树形dp上的公式转换
④和期望有关的树形dp + 一点排列组合的知识
⑤
⑥
⑦
⑧
⑨
⑩
一:Codeforces Round #364 (Div. 1) B
http://codeforces.com/problemset/problem/700/B
题目大意:给你一棵树,给你k个树上的点对。找到k/2个点对,使它在树上的距离最远。问,最大距离是多少?
思路:我们可以把树上的这个分成两个集合,然后两边的点的数目相等。符合这个条件的就是树的重心,所以我们只需要找到树的中心就行啦。
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
/*
题目大意:
给你一棵树,给你k个树上的点对。找到k/2个点对,使它在树上的距离最远。
问,最大距离是多少?
*/
const int maxn = + ;
int n, k;
vector<int> G[maxn];
bool vis[maxn];
int dp_cnt[maxn]; int dfs_cnt(int u, int fa){
int cnt = vis[u];
for (int i = ; i < G[u].size(); i++){
int v = G[u][i];
if (v == fa) continue;
cnt += dfs_cnt(v, u);
}
return dp_cnt[u] = cnt;
} void dfs_ce(int u, int fa, int &ce, int &maxcnt, int treesize){
int tmp = treesize - dp_cnt[u];
for (int i = ; i < G[u].size(); i++){
int v = G[u][i];
if (v == fa) continue;
tmp = max(tmp, dp_cnt[v]);
dfs_ce(v, u, ce, maxcnt, treesize);
}
if (maxcnt > tmp){
maxcnt = tmp; ce = u;
}
} LL ans;
void dfs(int u, int fa, int len){
if (vis[u]) {
ans = 1LL * len + ans;
}
for (int i = ; i < G[u].size(); i++){
int v = G[u][i];
if (v == fa) continue;
dfs(v, u, len + );
}
} int main(){
scanf("%d%d", &n, &k);
for (int i = ; i <= k * ; i++){
int u; scanf("%d", &u);
vis[u] = true;
}
for (int i = ; i < n; i++){
int u, v; scanf("%d%d", &u, &v);
G[u].pb(v); G[v].pb(u);
}
int treesize = dfs_cnt(, -);
int cetroid, maxcnt = maxn;
dfs_ce(, -, cetroid, maxcnt, treesize);
dfs(cetroid, -, );
printf("%lld\n", ans);
return ;
}
关键:寻找题目关键问题所在(分成两个数目相同的点集),然后探究树的性质
二:http://codeforces.com/contest/734/problem/E
题目大意:给你一棵树,树上每个点都是黑色或者是白色,每次有一个操作,选取一个点,把周围和它相邻的所有点的颜色都翻转一次,问最少需要几次操作才能让这棵树变成同一种颜色?
思路:我刚开始以为就是单纯的树形dp的,于是我刚开始定义dp(i,j)表示i下面的所有子树都变成颜色j需要的最少操作次数。然而发现状态转移的时候完全转移不了。然后表示虽然想到了缩点,但是感觉我这个dp定义的没有什么问题呀,然后就死在这里了,2333
看了一下官方题解,官方题解上面说:缩点以后,我们可以发现,每次操作以后再缩点,至少可以让反转以后再缩点的树和之前的树相比,结点数至少少了2.
然后我们再次发现,缩点所需要的最多次数,一定是直径上面点的个数。然后我们发现缩点的次数最少操作次数一定是>=(d+1)/2的,所以就可以很轻松的用两次dfs遍历找到直径了。
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = 2e5 + ;
int n, pos, maxdeep, ans;
vector<int> G[maxn];
int color[maxn], dp[maxn]; int dfs(int u, int fa, int deep){
if (deep > maxdeep){
maxdeep = deep; pos = u;
}
for (int i = ; i < G[u].size(); i++){
int v = G[u][i];
if (v == fa) continue;
dfs(v, u, deep + (color[u] != color[v]));
}
} void dfs_dia(int u, int fa, int deep){
if (deep > ans) ans = deep;
for (int i = ; i < G[u].size(); i++){
int v = G[u][i];
if (v == fa) continue;
dfs_dia(v, u, deep + (color[u] != color[v]));
}
} int main(){
cin >> n;
for (int i = ; i <= n; i++) scanf("%d", color + i);
for (int i = ; i < n; i++){
int u, v; scanf("%d%d", &u, &v);
G[u].pb(v), G[v].pb(u);
}
dfs(, -, );
dfs_dia(pos, -, );
printf("%d\n", (ans + ) / );
return ;
}
三:zstu oj 4248 链接:戳这里
题目大意:给你一棵以1位根的,边有权值的树,权值定为cost,每个点也有一个val。定义dis(i,j)表示i~j的所有路径权值和。如果存在dis(i,j) < val(i)-val(j),那么所有j的子树都被减去。问最后还有多少个节点?
思路:
设path(u)指从根到u这个节点的边权和,
dis(u,v) = path(v)-path(u) < val(u)-val(v)
val(v)<val(u)+path(u)-Path(v)
所以我们从根开始搜只要一路维护一个max{val(u)+path(u)}即可
关键:公式转化,讲道理应该很快要想到O(n)的方法,然后这个公式一定是和前面传下来的数值有关的(唉,我好菜啊)
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = 1e5 + ;
struct Node{
int to; LL cost;
Node(int to = , LL cost = ): to(to), cost(cost){}
};
vector<Node> G[maxn];
int dp[maxn];
LL val[maxn], path[maxn];
int n; int dfs_cnt(int u, int fa, LL p){
dp[u] = , path[u] = p;
for (int i = ; i < G[u].size(); i++){
int v = G[u][i].to;
if (v == fa) continue;
dp[u] += dfs_cnt(v, u, p + G[u][i].cost);
}
return dp[u];
} int dfs(int u, int fa, LL maxval){
int cnt = ;
for (int i = ; i < G[u].size(); i++){
int v = G[u][i].to;
if (v == fa) continue;
if (maxval > path[v] + val[v]) cnt += dp[v];
else cnt += dfs(v, u, path[v] + val[v]);
}
return cnt;
} int main(){
int t; cin >> t;
while (t--){
memset(path, , sizeof(path));
scanf("%d", &n);
for (int i = ; i <= n; i++) G[i].clear();
for (int i = ; i < n; i++){
int u, v; LL c;
scanf("%d%d%lld", &u, &v, &c);
G[u].pb(Node(v, c)); G[v].pb(Node(u, c));
}
for (int i = ; i <= n; i++)
scanf("%lld", val + i);
dfs_cnt(, -, );
int ans = n - dfs(, -, val[] + path[]);
printf("%d\n", ans);
}
return ;
}
四: 链接:戳这里
题目大意:给你一棵有n个节点的树,根是1,每次我们都从1出发,并且走到他的儿子的可能性都是随机的。每一个儿子的权值是根据走的不同的路径来计算的,走的路径不同,儿子的权值就不同(这一点用cf里面的那个代码来表示)
let starting_time be an array of length n
current_time = 0
dfs(v):
current_time = current_time + 1
starting_time[v] = current_time
shuffle children[v] randomly (each permutation with equal possibility)
// children[v] is vector of children cities of city v
for u in children[v]:
dfs(u)
问,最后请计算每一个节点位置的权值。
思路:
其实单单的看到这道题我是很害怕的,因为我很害怕这种求什么期望啊这类的问题。但是感觉概率论这门课上了以后,感觉对数学期望有了一个新的认识,貌似不是那么怕了,然后推导了一下式子以后发现,这道题并没有和我想象中一样那么难。
首先,我们计算出每个节点,他下面的子节点的个数,然后我是列出了样例一中的②、④、⑥、③这五个节点的数学期望的计算方式。然后我们可以得到一个数学期望的公式
目前节点的数学期望 = 父亲节点的数学期望+1+(父亲节点所有孩子的节点和 - 目前节点的size)/2.
该公式来的过程如下:
假定父亲节点是fa,父亲节点下面儿子的个数为m,父亲节点的下面的晚辈的总个数(包括自身)是fasize,儿子节点为child,儿子节点的下面晚辈的总个数(包括自身)是childsize,儿子节点下面的孙子的个数为n。
然后目前我们可以发现,当前我们停留的点为child,那么,child到fa这条路经过的次数一定是2^(n-1)次,所以,除了目前这个child外,其他儿子节点每个的贡献次数都为2^n-2次,所以我们得到如下的递推式:
父亲节点的期望值+(2^(n-1) + (其他儿子的size和) * 2^(n-2)) / (2^(n-1)),因此就得到上面的递推式啦
//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = 1e5 + ;
vector<int> road[maxn];
int n;
LL tree[maxn];///包括当前节点
double res[maxn]; LL dfs_size(int u){
tree[u] = ;
for (int i = ; i < road[u].size(); i++){
tree[u] += dfs_size(road[u][i]);
}
return tree[u];
} void dfs_ans(int u, int fa){
res[u] = 1.0 + res[fa];
if (fa != ) res[u] += 1.0 * (tree[fa] - - tree[u]) / 2.0;
for (int i = ; i < road[u].size(); i++)
dfs_ans(road[u][i], u);
} int main(){
cin >> n;
for (int i = ; i <= n; i++){
int x; scanf("%d", &x);
road[x].push_back(i);
}
dfs_size();
dfs_ans(, );
for (int i = ; i <= n; i++){
printf("%.6f\n", res[i]);
}
return ;
}
关键:熟悉数学期望,掌握排列组合
五:
六:
七:
八:
九:
十:
树形dp的进阶 (一)的更多相关文章
- 算法进阶面试题05——树形dp解决步骤、返回最大搜索二叉子树的大小、二叉树最远两节点的距离、晚会最大活跃度、手撕缓存结构LRU
接着第四课的内容,加入部分第五课的内容,主要介绍树形dp和LRU 第一题: 给定一棵二叉树的头节点head,请返回最大搜索二叉子树的大小 二叉树的套路 统一处理逻辑:假设以每个节点为头的这棵树,他的最 ...
- [提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)
转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7337179.html 树形DP是一种在树上进行的DP相对比较难的DP题型.由于状态的定义多种多样,因此解法也五 ...
- 浅谈关于树形dp求树的直径问题
在一个有n个节点,n-1条无向边的无向图中,求图中最远两个节点的距离,那么将这个图看做一棵无根树,要求的即是树的直径. 求树的直径主要有两种方法:树形dp和两次bfs/dfs,因为我太菜了不会写后者这 ...
- 题解 poj3585 Accumulation Degree (树形dp)(二次扫描和换根法)
写一篇题解,以纪念调了一个小时的经历(就是因为边的数组没有乘2 phhhh QAQ) 题目 题目大意:找一个点使得从这个点出发作为源点,流出的流量最大,输出这个最大的流量. 以这道题来介绍二次扫描和换 ...
- 初涉树形dp
算是一个……复习以及进阶? 什么是树形dp 树形dp是一种奇妙的dp…… 它的一个重要拓展是和各种树形的数据结构结合,比如说在trie上.自动机上的dp. 而且有些时候还可以拓展到环加外向树.仙人掌上 ...
- Luogu P2458 [SDOI2006]保安站岗【树形Dp】
题目描述 五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序. 已知整个地下超市的所有通道呈一棵树的形状:某些通道之间可以互 ...
- poj3417 LCA + 树形dp
Network Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 4478 Accepted: 1292 Descripti ...
- COGS 2532. [HZOI 2016]树之美 树形dp
可以发现这道题的数据范围有些奇怪,为毛n辣么大,而k只有10 我们从树形dp的角度来考虑这个问题. 如果我们设f[x][k]表示与x距离为k的点的数量,那么我们可以O(1)回答一个询问 可是这样的话d ...
- 【BZOJ-4726】Sabota? 树形DP
4726: [POI2017]Sabota? Time Limit: 20 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 128 Solved ...
随机推荐
- Ubuntu环境下No module named '_tkinter'错误的解决
在Ubuntu环境下运行下面代码: import matplotlib as plt 出现以下错误: No module named '_tkinter' 解决方法: sudo apt-get ins ...
- UVALive - 6887 Book Club 有向环的路径覆盖
题目链接: http://acm.hust.edu.cn/vjudge/problem/129727 D - Book Club Time Limit: 5000MS 题意 给你一个无自环的有向图,问 ...
- 《剑指offer》--- 两个链表的第一个公共结点
本文算法使用python3实现 1. 问题 输入两个链表,找出它们的第一个公共结点. 时间限制:1s:空间限制:32768K 2 思路描述 使用两个指针 $ p1,p2 $ 分别指向两个链 ...
- 开发环境解决 kafka Failed to send messages after 3 tries
新建了一个kafka集群,在window下写了一个简单的producer做测试,结果遇到了消息发送失败的问题,代码如下: Properties props = new Properties(); pr ...
- 1029对c语言文法的理解
<程序>→<外部声明>|<程序><外部声明> <外部声明>→<函数定义>|<声明> <函数定义>→< ...
- 31. Ubuntu15.04系统中如何启用、禁用客人会话
https://jingyan.baidu.com/article/046a7b3edf9639f9c27fa995.html 31. Ubuntu15.04系统中如何启用.禁用客人会话 听语音 | ...
- 在linux下编译线程程序undefined reference to `pthread_create'
由于是Linux新手,所以现在才开始接触线程编程,照着GUN/Linux编程指南中的一个例子输入编译,结果出现如下错误:undefined reference to 'pthread_create'u ...
- PHP中关于取模运算及符号
执行程序段<?php echo 8%(-2) ?>,输出结果是: %为取模运算,以上程序将输出0 $a%$b,其结果的正负取决于$a的符号. echo ((-8)%3); //将 ...
- java面试及答案
优秀总结博客 mybatis总结 java并发包相关 一.Java基础 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. hashmap3.反射中,Class. ...
- BZOJ 1565 植物大战僵尸(拓扑排序+最大权闭合子图)
图中的保护关系就类似于最大权闭合子图.即你想杀x,你就一定要杀掉保护x的点,那么把x向保护它的点连边.那么题目就转化成了最大权闭合子图的问题. 但是这个图有点特殊啊... 考虑有环的情况,显然这个环以 ...