最大疯子树:树形DP优化:二次扫描+换根法(poj3585)

    相信看这篇文的人应该是会一些简单的线性树形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

发布了58 篇原创文章 · 获赞 34 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37666409/article/details/79656125