第一次做树形dp,居然1Y了,而且发现注释真是好东西,写了注释自己也不容易犯错。对于DP来说更是如此,因为DP的状态表述得越不含糊,就越不容易写错转移方程。
dp说到底就是递推,往前推或者往后推都不是关键,只要可以推就行。
而树形dp就是推的过程发生树上面。
对于一个点i,假设存在一个距离它最远的一个点j。要从i出发走到j,第一步只有两种可能,要么从i走到i的某个儿子节点,要么从i走到i的父亲。根据这一点,我们可以做个分类:
当第一步是 从i走到i的某个儿子s的时候,接下来,我们要做的就是从s一直往下走,走得越远越好。
当第一步是 从i走到i的父亲f的时候,接下来,我们有3种选择,
(1)我们要么停住不走了
(2)要么就从f走到f的父亲F,然后再从F出发继续走得越远越好。
(3)要么就从f出发,往下走到f的某个儿子k,然后从k出发继续走得越远越好。 (case#)
很明显,这个过程是一个具有最优质结构的东西。从上面的叙述,我们可以发现,我们需要的信息有:
longestSon[i] 表示从i出发,并且步数不能为0,并且第一步是往下走到某个儿子,能够走到的最远距离
secondlongestSon[i] 表示从i出发,并且步数不能为0,并且第一步是往下走到某个儿子,能够走到的第二远的距离
longestFather[i] 表示从i出发,并且步数不能为0,并且第一步是往上走到i的父亲,能够走到的最远距离
为什么需要secondlongestSon[i]呢?
因为(case#)有点特殊性,当我们从i走到f以后,如果我们想要继续往下走到f的某个儿子k,很明显,我们要选择一个使得longestSon[k]最大的k,但是这里有个约束条件,就是 k必须不能等于i!
所以呢,如果i恰好就是f的所有儿子里具有最大longestSon的节点,我们也不能令 k = i,这个时候,我们就需要令k 等于 使得longestSon[k]第二最大的k,这就是secondlongestSon[i]的用途。
这题本来以为写起来会很烦,递归就是好,最后写出来发现代码量还可以接受哈
#include <cstdio> #include <vector> #include <algorithm> using namespace std; // 助手函数 ============== {{{ template<typename T, typename T2> void makeVector(T & a, int len, const T2 & initV) { a.resize(len); for (int i = 0; i < a.size(); ++i) a[i] = initV; } // ======================= }}} // 图 =================== {{{ struct Edge { int adj; Edge* next; int length; }; struct Graph { vector<Edge*> vs; void init(int pointNum) { vs.resize(pointNum); for (int i = 0; i < vs.size(); ++i) { vs[i] = NULL; } } Edge* newEdge() { return new Edge; } void add(int st, int ed, int len) { Edge* e = newEdge(); e->adj = ed; e->next = vs[st]; vs[st] = e; e->length = len; } Edge* get(int i) { return vs[i]; } }; // ======================= }}} struct LengthAndId { int length; int id; }; inline LengthAndId makeLengthAndId(int L, int id) { LengthAndId a; a.length = L; a.id = id; return a; } // 全局变量 ====================================== const int infinite = 2000000000; // longestSon[i] 表示从点i出发,向着它的某个儿子走,走到最长距离,那个时候的目的地。注意的是,目的地必须是自己的真儿子,不能是自己 // 如果这个数为负数,表示它是个叶子,因此一个儿子都没有 vector< LengthAndId > longestSon; // longestSon[i] 表示从点i出发,向着它的某个儿子走,走到第二最长距离,那个时候的目的地。注意的是,目的地必须是自己的真儿子,不能是自己 // 如果这个数为负数,表示没有第二个儿子 vector< LengthAndId > secondLongestSon; //longestFather 表示从点i出发向上走,第一步必须是走到它的父亲,然后可以继续走,一直走到最长距离,那个时候的目的地 //如果这个数是负数,表示这个点是整棵大树的根 vector< LengthAndId > longestFather; vector< int > father; //father[i]表示节点i的父亲节点 const char WHITE = 'W'; const char BLACK = 'B'; const char GRAY = 'G'; vector< char > color; Graph G; //图 int N; vector< int > longestFromMe; // ======================================== struct __CmpByLength { bool operator() (const LengthAndId &a, const LengthAndId &b) const { return a.length < b.length; } } cmpByLength; //这个函数计算某个点的longestSon 和 secondLongestSon; void solve(int me) { color[me] = GRAY; Edge* e; vector<LengthAndId> sons; sons.clear(); for (e = G.get(me); e; e = e->next) { if (color[e->adj] != WHITE) continue; solve(e->adj); if (longestSon[e->adj].length < 0) { //如果那个儿子是个叶节点 sons.push_back(makeLengthAndId(e->length, e->adj)); } else { sons.push_back(makeLengthAndId(e->length + longestSon[e->adj].length, e->adj)); } } if (sons.size() == 0) { //如果没有儿子 longestSon[me] = makeLengthAndId(-infinite, 0); secondLongestSon[me] = makeLengthAndId(-infinite, 0); } else if (sons.size() == 1) { //有一个儿子 longestSon[me] = makeLengthAndId(sons.back().length, sons.back().id); secondLongestSon[me] = makeLengthAndId(-infinite, 0); } else if (sons.size() >= 2) { //有两个儿子或以上 sort(sons.begin(), sons.end(), cmpByLength); longestSon[me] = makeLengthAndId(sons.back().length, sons.back().id); secondLongestSon[me] = makeLengthAndId(sons[sons.size() - 2].length, sons[sons.size() - 2].id); } color[me] = BLACK; } //这个函数计算某个点的longestFather, me 是点的id,len是连接me和父亲的边长度 void solve2(int me, int len) { color[me] = GRAY; if (father[me] < 0) { longestFather[me] = makeLengthAndId(-infinite, 0); goto GO_TO_MY_SONS; //为了避免嵌套太多,用一下goto! } //当我们从me走到me的父亲的时候,可以有三种选择:停住,继续往上走,往下走(但是不能走回me) //停住: longestFather[me] = makeLengthAndId(len, father[me]); //继续往上走 if (longestFather[father[me]].length > 0) { if (longestFather[me].length < longestFather[father[me]].length + len) longestFather[me] = makeLengthAndId(longestFather[father[me]].length + len, father[me]); } //最复杂的情况,向下走 //如果me的父亲只有一个节点,就不能向下走 if (!(secondLongestSon[father[me]].length < 0)) { if (longestSon[father[me]].id != me) { if (longestFather[me].length < len + longestSon[father[me]].length) longestFather[me] = makeLengthAndId(len + longestSon[father[me]].length, father[me]); } else { if (longestFather[me].length < len + secondLongestSon[father[me]].length) longestFather[me] = makeLengthAndId(len + secondLongestSon[father[me]].length, father[me]); } } GO_TO_MY_SONS: //搞定了自己,可以去搞儿子了 for (Edge* e = G.get(me); e; e = e->next) { if (color[e->adj] != WHITE) continue; solve2(e->adj, e->length); } //这个时候,我们已经有足够信息来知道距离me最远的那个点的距离了! longestFromMe[me] = 0; if (longestSon[me].length > 0) longestFromMe[me] = longestSon[me].length; if (longestFather[me].length > 0 && longestFather[me].length > longestFromMe[me]) longestFromMe[me] = longestFather[me].length; //走人! color[me] = BLACK; } void input() { scanf("%d", &N); G.init(N + 1); makeVector(father, N + 1, 0); father[1] = -infinite; int i; for (i = 2; i <= N; ++i) { int f, L; scanf("%d %d", &f, &L); G.add(f, i, L); // G.add(i, f, L); father[i] = f; } } void outputAns() { int i; for (i = 1; i <= N; ++i) { printf("%d\n", longestFromMe[i]); } } void run() { input(); makeVector(color, N + 1, WHITE); makeVector(longestSon, N + 1, makeLengthAndId(0, 0)); makeVector(secondLongestSon, N + 1, makeLengthAndId(0, 0)); solve(1); makeVector(longestFather, N + 1, makeLengthAndId(0,0)); makeVector(color, N + 1, WHITE); makeVector(longestFromMe, N + 1, 0); solve2(1, 0); outputAns(); } int main() { run(); return 0; } // vim:foldmethod=marker