①树的重心的性质的运用
②缩点以后寻找规律 树的直径!
③树形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 ") /* 题目大意: 给你一棵树,给你k个树上的点对。找到k/2个点对,使它在树上的距离最远。 问,最大距离是多少? */ const int maxn = 200000 + 5; 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 = 0; 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 = 0; 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 = 0; i < G[u].size(); i++){ int v = G[u][i]; if (v == fa) continue; dfs(v, u, len + 1); } } int main(){ scanf("%d%d", &n, &k); for (int i = 1; i <= k * 2; i++){ int u; scanf("%d", &u); vis[u] = true; } for (int i = 1; i < n; i++){ int u, v; scanf("%d%d", &u, &v); G[u].pb(v); G[v].pb(u); } int treesize = dfs_cnt(1, -1); int cetroid, maxcnt = maxn; dfs_ce(1, -1, cetroid, maxcnt, treesize); dfs(cetroid, -1, 0); printf("%lld ", ans); return 0; }
关键:寻找题目关键问题所在(分成两个数目相同的点集),然后探究树的性质
二: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 ") const int maxn = 2e5 + 10; 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 = 0; 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 = 0; 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 = 1; i <= n; i++) scanf("%d", color + i); for (int i = 1; i < n; i++){ int u, v; scanf("%d%d", &u, &v); G[u].pb(v), G[v].pb(u); } dfs(1, -1, 0); dfs_dia(pos, -1, 0); printf("%d ", (ans + 1) / 2); return 0; }
三: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 ") const int maxn = 1e5 + 5; struct Node{ int to; LL cost; Node(int to = 0, LL cost = 0): 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] = 1, path[u] = p; for (int i = 0; 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 = 0; for (int i = 0; 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, 0, sizeof(path)); scanf("%d", &n); for (int i = 1; i <= n; i++) G[i].clear(); for (int i = 1; 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 = 1; i <= n; i++) scanf("%lld", val + i); dfs_cnt(1, -1, 0); int ans = n - dfs(1, -1, val[1] + path[1]); printf("%d ", ans); } return 0; }
四: 链接:戳这里
题目大意:给你一棵有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 ") const int maxn = 1e5 + 5; vector<int> road[maxn]; int n; LL tree[maxn];///包括当前节点 double res[maxn]; LL dfs_size(int u){ tree[u] = 1; for (int i = 0; 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 != 0) res[u] += 1.0 * (tree[fa] - 1 - tree[u]) / 2.0; for (int i = 0; i < road[u].size(); i++) dfs_ans(road[u][i], u); } int main(){ cin >> n; for (int i = 2; i <= n; i++){ int x; scanf("%d", &x); road[x].push_back(i); } dfs_size(1); dfs_ans(1, 0); for (int i = 1; i <= n; i++){ printf("%.6f ", res[i]); } return 0; }
关键:熟悉数学期望,掌握排列组合
五:
六:
七:
八:
九:
十: