相信看这篇文的人应该是会一些简单的线性树形dp的吧……
如果有不会的请先看看树形dp基础吧……比如这道题没有上司的舞会
其实之所以想写这篇文是因为前段时间被教练骗去叫去参加一场UESTC组织主办的线下赛,叫什么……2018青少年信息学科普活动春季体验营,然后测试的时候第一题我是用的换根法A的,于是决定皮一皮写一写。
先上题面:
最大疯子树
【题目描述】
给定一棵 n 个结点的树,结点编号为 1~n,i 号结点的权重记为 wi(每个点的权值各不相同)。我们定义一个“疯子树”为:
1. 是一个联通子图。
2. 我们将子图内的点按照权重从小到大排序后序列为 b1,b2,…,bm,对于任意的 i(i<m),bi到 bi+1最短路径(不含 bi和 bi+1)上的结点的权值都小于等于 i wb 。
输出包含结点最多的“疯子树”的结点数。
【输入格式】
数据有多组 case,文件以 EOF 结束,每组第一行输入一个 n 表示树的节点数;
接下来一行包含 n 个整数,第 i 个数表示 i 号结点的权重 wi;接下行 n-1 行,第
i 行包含 2 个整数 ui, vi,表示 ui和 vi有一条边。
【输出格式】
对于每组 case 输出一行,包含一个整数,表示包含结点最多的“疯子树”
的结点数。
【样例输入】
5
1 4 2 3 5
1 2
2 3
3 4
4 5
5
2 5 4 1 3
1 2
2 3
3 4
4 5
【样例输出】
4
4
【数据范围】
对于 10%的数据有所有组的 n 之和 n≤20。
对于 30%的数据有所有组的 n 之和 n≤2000。
对于 100%的数据有所有组的 n 之和 n≤200000,0 < wi≤100000000,n 为正
整数。
咋一看,哎呀好复杂,妈呀不会,弃了弃了!(事实上本校有的同学真的就这样和100分擦肩而过),然而如果你好好想一想,可以发现几点:
疯子树肯定还是一棵树
所以,所谓的最短路径就是吓唬你的,树上两点之间有且只有一条路径。
b1和b2必须是相邻的,否则不可能是一棵疯子树。
再想一想,用同样的方式构造剩下的点的话,那么可以得到一个结论:
如果以一棵疯子树以键值最小的那个节点为根,那么对于从根到叶的每一条简单路径上,键值是不严格递增的!
所以可以很轻松的得到一个n^2的方法:
inline void dfs(int x, int fa) {
f[x] = 1;
for(int i = h[x]; i; i = E[i].nxt) {
int v = E[i].to;
if(v == fa) continue;
dfs(v, x);
if(val[x] <= val[v]) f[x] += f[v];
}
}
而主函数中的调用就是:
int ans = -0x3f3f3f3f;
for(int i = 1; i <= n; i++) dfs(i, 0), ans = max(ans, f[i]);
printf("%d\n", ans);
但是很明显这样是过不了的!所以想个办法优化,上述过程很明显,就是分别以每个节点为根dfs一遍得出以该节点为根时的最大疯子树,而可以优化的点就在于换根的过程,思考换根时有哪些东西需要更新,哪些需要继承
f[x]肯定是没有必要再求的,所以需要考虑的就是原本在不以x为根时的父亲节点fa对答案的贡献,如果val[fa] >= val[x],那么就是可转移的,否则不做处理。全部代码如下:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200200;
const int MAXE = 400400;
const int INF = 0x3f3f3f3f;
struct Edge {
int to, nxt;
Edge() {}
Edge(int _to, int _nxt) : to(_to), nxt(_nxt) {}
}E[MAXE];
int h[MAXN], cnt, d[MAXN], f[MAXN], val[MAXN];
int n;
inline void add_edge(int x, int y) {
E[++cnt] = Edge(y, h[x]), h[x] = cnt;
E[++cnt] = Edge(x, h[y]), h[y] = cnt;
}
inline void dfs1(int x, int fa) {
f[x] = 1;
for(int i = h[x]; i; i = E[i].nxt) {
int v = E[i].to;
if(v == fa) continue;
dfs1(v, x);
if(val[x] <= val[v]) f[x] += f[v];
}
}
inline void dfs2(int x, int fa) {
d[x] = f[x];
if(val[fa] >= val[x]) d[x] += d[fa];
for(int i = h[x]; i; i = E[i].nxt) {
int v = E[i].to;
if(v == fa) continue;
dfs2(v, x);
}
}
inline void init() {
memset(h, 0, sizeof(h)); memset(d, 0, sizeof(d));
memset(f, 0, sizeof(f)); cnt = 0;
}
signed main() {
while(scanf("%d", &n) != EOF) {
init();
for(int i = 1; i <= n; i++) scanf("%d", val + i);
for(int i = 1; i < n; i++) {
int a, b;
scanf("%d%d", &a, &b);
add_edge(a, b);
}
dfs1(1, 0);
int ans = -INF;
dfs2(1, 0);
for(int i = 1; i <= n; i++) ans = max(ans, d[i]);
printf("%d\n", ans);
}
return 0;
}
那么现在考虑一道奇怪的题poj3585
这题看着只有那么像网络最大流了……但是考虑两点:
1.数据范围200000
2.这是一棵树
网络流pass,考虑树形dp,根据网络最大流的定义,我们需要维护根到叶的每条简单路径上的最小值,然后换根的时候注意如何维护父节点的贡献就好了:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int MAXN = 200200;
const int MAXE = 400400;
const int INF = 0x3f3f3f3f;
struct Edge {
int to, nxt, len;
Edge() {}
Edge(int _to, int _nxt, int _len) : to(_to), nxt(_nxt), len(_len) {}
}E[MAXE];
int h[MAXN], cnt, f[MAXN], d[MAXN], val[MAXN], deg[MAXN];
int n, T;
inline void add_edge(int x, int y, int z) {
E[++cnt] = Edge(y, h[x], z), h[x] = cnt;
E[++cnt] = Edge(x, h[y], z), h[y] = cnt;
}
void dfs1(int x, int fa) {
d[x] = 0;
for(int i = h[x]; i; i = E[i].nxt) {
int v = E[i].to;
if(v == fa) continue;
dfs1(v, x);
if(deg[v] == 1) d[x] += E[i].len;
else d[x] += min(d[v], E[i].len);
}
}
void dfs2(int x, int fa) {
for(int i = h[x]; i; i = E[i].nxt) {
int v = E[i].to;
if(v == fa) continue;
if(deg[x] == 1) f[v] = d[v] + E[i].len;
else f[v] = d[v] + min(f[x] - min(d[v], E[i].len), E[i].len);
dfs2(v, x);
}
}
void init() {
memset(h, 0, sizeof(h));
memset(d, 0, sizeof(d));
memset(f, 0, sizeof(f));
memset(deg, 0, sizeof(deg));
cnt = 0;
}
signed main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
init();
for(int i = 1; i < n; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add_edge(a, b, c); deg[a]++; deg[b]++;
}
dfs1(1, 0);
f[1] = d[1];
dfs2(1, 0);
int res = 0;
for(int i = 1; i <= n; i++) res = max(res, f[i]);
printf("%d\n", res);
}
return 0;
}
疯子树那题给一个数据和题面的链接
密码是s6vc