ACM-ICPC Regionals 2011 Asia - Fuzhou C Bob's Race
POJ4003
树形dp 单调队列
http://poj.org/problem?id=4003
给定一棵N个节点的树,数列d[i] (1 <= i <= N)表示树中离点i最远的点离点i的距离。现给定Q,求d[i]的最长连续子序列的长度,使得其满足该子序列中最大最小值之差不超过Q。
这个题属于比较生硬的凑算法的题……问题可以分为两部分:
1. 求数列d[i]。这里用的是非常经典的一种树形dp方法,我以前不知道叫什么,今天据章总和戴牛说叫“上下树”(好猥琐……),长春邀请赛也出了,有空可以写个专题。这种方法在对树的每个节点进行全局统计且要求复杂度为O(N)时经常用到。“上下树”的名称大概来源于这种树形dp进行两次dp,第一次自底向上(叶到根),第二次自顶向下(根到叶)。对于此题,考虑将一个点拎出来作树根后,某个点v的最远距离,要么是v向子孙节点走的最远距离,要么是其父节点u不经过(u,v)的最远距离+w(u,v)。可以把边(u,v)看作分界,u在左v在右,则前者是v往右走的最远距离,后者是v往左走的最远距离。
因此,第一次树形dp要求出点u向子孙节点走的最远距离max1[u],向哪个子节点走最远(设为maxv[u])以及向子孙节点走的次远距离max2[u](为何要求接下来说明)。第二次树形dp对于点v,d[v]=max(x, max1[v]),其中x为v的父节点不经过(u,v)的最远距离+w(u,v)。根据v是否是maxv[u],x是不一样的,因此可能用到向哪个子节点走最远(设为maxv[u])以及向子孙节点走的次远距离max2[u]。
2. 求d[i]的最长连续子序列的长度。网上很多说用RMQ做,我太弱搞不懂为啥会用到RMQ,我用的是单调队列做的。
显然应考察以d[i]结尾的最长子序列。假设已知以d[i-1]结尾的最长子序列X,为了求d[i]结尾的最长子序列Y,我们还需要知道X集中的最大数,设为d[j]。若|d[i]-d[j]|>Q则Y的起始下标>j且应继续考察d[j]后的最大数d[j']。若|d[i]-d[j']|>Q则Y的起始下标>j'且应继续考察d[j']后的最大数d[j'']等等。类似的还需要知道X集中的最小数d[k],d[k]后最小数d[k'],d[k']后最小数d[k'']等。这样容易得到Y。这种信息要结构化存储,而且加入d[i]后还要O(1)转移,你想到什么,反正我想到的是单调队列。事实上d[j],d[j'],d[j'']...构成的就是一个单调增队列。
顺便,那次比赛留下了无可挽回的遗憾,家乡真是我永远的伤心之地……
注意,STL的deque会被POJ猥琐卡常数,必须手写。
#include <cstdio> #include <cstring> #include <vector> #include <queue> #include <algorithm> using namespace std; int N, M, Q, ans, st; vector<int> adj[50050], w[50050]; int max1[50050], max2[50050], maxv[50050]; int d[50050]; int inc[50050],ih, it, dec[50050], dh, dt; void init() { for (int i = 1; i <= N; ++i) { adj[i].clear(); w[i].clear(); } } inline void ae(int u, int v, int d) { adj[u].push_back(v); w[u].push_back(d); adj[v].push_back(u); w[v].push_back(d); } void dfs1(int f, int u) { max1[u] = max2[u] = maxv[u] = 0; int v, tmp; for (int i = 0; i < adj[u].size(); ++i) { v = adj[u][i]; if (v==f) continue; dfs1(u, v); tmp = max1[v]+w[u][i]; if (tmp >= max1[u]) { max2[u] = max1[u]; max1[u] = tmp; maxv[u] = v; } else if (tmp > max2[u]) max2[u] = tmp; } } void dfs2(int f, int u, int l) { d[u] = max(l, max1[u]); int v, tmp; for (int i = 0; i < adj[u].size(); ++i) { v = adj[u][i]; if (v==f) continue; if (v != maxv[u]) dfs2(u, v, d[u]+w[u][i]); else dfs2(u, v, max(max2[u], l)+w[u][i]); } } int main() { int i, j, k, u, v; while (scanf("%d%d", &N, &M), N||M) { init(); for (i = 1; i < N; ++i) { scanf("%d%d%d", &u, &v, &k); ae(u, v, k); } dfs1(0, 1); dfs2(0, 1, 0); while (M--) { scanf("%d", &Q); ans = 1; ih = it = dh = dt = 0; inc[it++] = 1; dec[dt++] = 1; st = 0; for (i = 2; i <= N; ++i) { j = k = st; while (ih!=it && abs(d[i]-d[inc[ih]])>Q) j = inc[ih++]; while (dh!=dt && abs(d[i]-d[dec[dh]])>Q) k = dec[dh++]; st = max(j, k); ans = max(ans, i-st); while (ih!=it && d[i]<=d[inc[it-1]]) it--; inc[it++] = i; while (dh!=dt && d[i]>=d[dec[dt-1]]) dt--; dec[dt++] = i; } printf("%d\n", ans); } } return 0; }