HNOI2014 世界树 基于虚树的树形动态规划
2016-06-06 22:59
405 查看
题目大意
给你一个N个节点的树,现在有Q组询问,对于第i组询问先给你一个Mi个树中的节点,设这些节点为关键点,对于树中的每个点都属于它最近的关键点,如有多个则选编号最小的那个。要求找出每个关键点包含了多少个树中的节点(包括自己)。N≤300000
Q≤3000000
∑Mi≤300000
解体思路
一看到题目,N,M那么大,而∑Mi≤300000,那么就知道这题的解法肯定跟M有关。为了减少树中节点的个数,虚树就是个能满足这个要求的数据结构,对于这题,我们需要考虑的就只有那M个点和它们两两见的Lca,而Lca的个数不会超过M个,那么假如虚树上的点都是关键点,那么们只需在虚树相邻的两个点的路径上找到一个分界点(这里指原树的路径,其实只需要记录一下深度就可以了),使分界点两边的点分配到这两个关键点中,这当然很简单,并且能线性的完成。可问题是,并不是虚树上的每个点都是关键点,还有关键点的Lca。
那么我们把虚树中的关键点成为黑点,其它称为白点。对于每个白点,我们需要先找出一个离它最近的黑点,作为控制它的点,这个可以用树形dp上下各扫一遍线性的完成。那我们考虑一下虚树上的每条边:
连接两个黑点:直接找分界点即可。
连接一个黑点,一个白点:如果控制这个白点的就是这个黑点,显然这条边上的所有点都属与这个黑点。如果不是那我们就求控制这个白点的黑点和这个黑点之间路径的分界点,显然这个分界点肯定在这条边上,那找到分界点后直接分配边上的点就可以了。
连接两个白点:找到控制这两个白点的黑点,那么就跟第二种情况一样了。
至此我们就有了线性求出每个关键点包含的点了,并且是与Mi相关,那么这题就完美解决了。
程序
//YxuanwKeith #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 3e5 + 5; int tot, Last[MAXN], Next[MAXN * 2], Go[MAXN * 2]; int N, Q, Time, L[MAXN], R[MAXN], Close[MAXN], tag[MAXN], Flag[MAXN], Num[MAXN], Clear[MAXN]; int Fa[MAXN][21], Deep[MAXN], Size[MAXN], Dist[MAXN], D[MAXN], P[MAXN], Pre[MAXN], Ans[MAXN]; bool Cmp(int A, int B) { return L[A] < L[B];} void Link(int u, int v) { Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v; } void Init() { scanf("%d", &N); for (int i = 1; i < N; i ++) { int u, v; scanf("%d%d", &u, &v); Link(u, v), Link(v, u); } } void Dfs(int Now, int Pre) { L[Now] = ++ Time, Fa[Now][0] = Pre; Size[Now] = 1, Deep[Now] = Deep[Pre] + 1; for (int p = Last[Now]; p; p = Next[p]) { int v = Go[p]; if (v == Pre) continue; Dfs(v, Now); Size[Now] += Size[v]; } R[Now] = Time; } void PreFa() { for (int j = 1; j <= 20; j ++) for (int i = 1; i <= N; i ++) Fa[i][j] = Fa[Fa[i][j - 1]][j - 1]; } int GetPre(int Now, int Goal) { for (int i = 20; i + 1; i --) if (Deep[Fa[Now][i]] >= Goal) Now = Fa[Now][i]; return Now; } int Lca(int u, int v) { if (Deep[u] < Deep[v]) swap(u, v); for (int i = 20; i + 1; i --) if (Deep[Fa[u][i]] >= Deep[v]) u = Fa[u][i]; if (u == v) return u; for (int i = 20; i + 1; i --) if (Fa[u][i] != Fa[v][i]) u = Fa[u][i], v = Fa[v][i]; return Fa[u][0]; } int Dis(int A, int B) { return Deep[A] + Deep[B] - Deep[Lca(A, B)] * 2; } void Update(int Now, int From, int Ask) { From = Close[From]; int dis = Dis(Now, From); if (tag[Now] != Ask || Dist[Now] > dis || Dist[Now] == dis && Close[Now] > From) { tag[Now] = Ask; Close[Now] = From; Dist[Now] = Dis(Now, From); } } void PreDown(int Ask, int New) { for (int i = 2; i <= New; i ++) Update(P[i], Pre[P[i]], Ask); } void PreUp(int Ask, int New) { for (int i = New; i; i --) { int Now = P[i]; for (int p = Last[Now]; p; p = Next[p]) Update(Now, Go[p], Ask); } } void Solve(int Id1, int Id2, int Side1, int Side2, int Below) { Num[Id2] += 1; if (Fa[Side2][0] == Side1) return; int Len = Dis(Id1, Id2) - 1; int Mid = GetPre(Side2, Deep[Id2] - (Len + 1) / 2); if (Len & 1 && Id2 > Id1) Mid = GetPre(Side2, Deep[Mid] + 1); Num[Id1] += Size[Below] - Size[Mid]; Num[Id2] += Size[Mid] - Size[Side2]; } void GetAns(int Now, int Ask) { int Son = 0; for (int p = Last[Now]; p; p = Next[p]) { int v = Go[p]; if (Clear[Close[v]] != Ask) Num[Close[v]] = 0, Clear[Close[v]] = Ask; int Below = GetPre(v, Deep[Now] + 1); Son += Size[Below]; if (Close[Now] == Close[v]) { Num[Close[Now]] += Size[Below] - Size[v] + 1; } else Solve(Close[Now], Close[v], Now, v, Below); GetAns(Go[p], Ask); } Num[Close[Now]] += (Size[Now] - Son - 1); } void Solve() { Dfs(1, 0); PreFa(); scanf("%d", &Q); for (int Ask = 1; Ask <= Q; Ask ++) { int M; memset(Last, 0, sizeof Last); tot = 0; scanf("%d", &M); for (int i = 1; i <= M; i ++) { scanf("%d", &P[i]); Ans[i] = P[i]; Flag[P[i]] = Ask; Close[P[i]] = P[i], tag[P[i]] = Ask, Dist[P[i]] = 0; } sort(P + 1, P + 1 + M, Cmp); int All = M, New = 0; for (int i = 1; i <= M - 1; i ++) P[++ All] = Lca(P[i], P[i + 1]); sort(P + 1, P + All + 1, Cmp); for (int i = 1; i <= All; i ++) if (P[i] != P[i - 1]) P[++ New] = P[i]; D[0] = 0; for (int i = 1; i <= New; i ++) { int Now = P[i]; Last[Now] = 0; while (D[0] && (R[Now] < L[D[D[0]]] || L[Now] > R[D[D[0]]])) -- D[0]; if (D[0] > 0) Link(D[D[0]], Now), Pre[Now] = D[D[0]]; D[++ D[0]] = Now; } PreUp(Ask, New); PreDown(Ask, New); Num[Close[P[1]]] = Size[1] - Size[P[1]] + 1, Clear[Close[P[1]]] = Ask; GetAns(P[1], Ask); for (int i = 1; i <= M; i ++) printf("%d ", Num[Ans[i]]); printf("\n"); } } int main() { freopen("worldtree.in", "r", stdin), freopen("worldtree.out", "w", stdout); Init(); Solve(); }
相关文章推荐
- java匿名内部类
- 【LeetCode】011 Swap Nodes in Pairs 两两换位
- Android的线程和线程池
- Linq使用Group By 1
- hibernate框架知识
- fseek的坑(跨平台注意)
- Servlet 生命周期、工作原理
- svn "cannot set LC_CTYPE locale" 问题以及LANG, LC_CTYPE, LC_ALL值的设置
- java多线程常见问题和解析
- 高可用系统开发可能遇到的问题
- 【leetcode】235. Lowest Common Ancestor of a Binary Search Tree
- Spark之DataFrame通过编码创建
- c++13周实验:输入/输出流的综合应用
- machine learning in action 之一 —— 环境配置
- DOM-----document对象
- Oracle一列的多行数据拼成一行显示字符
- LeetCode 141 Linked List Cycle(循环链表)(hash / set)
- java : tomcat 6.0 应用 JSTL 正确做法
- for 循环 and while 循环(三)
- [自制模板引擎]写一个迷你模板引擎