【GDOI2019Day1模拟2019.4.28】盗梦空间(虚树+树形dp+树链剖分)

10 篇文章 0 订阅
3 篇文章 0 订阅

Description:

在这里插入图片描述

1<=n,sum(k)<=1e5

题解:

首先建出虚树。

然后考虑把所有的点分为三类:
1.虚树上的点
2.虚树上边(不包括虚树点)的点和伸出去的子树
3.不属于1、2类的点

对于虚树上的点,使用树形dp求出每一个点所有子树中第1长的和第2长的。

不属于1、2类点,可以对每个点所有伸出的子树预处理深度排序后的结果,或者用multiset动态维护

最难的在于第2类点

假设虚树有一条边x,y(dep[x]<dep[y])
x的子树中除y最长是X,y的子树中除x最长是Y

知道了这个解个方程就可以确定分界点,分界点下的到x近,分界点上的到y近。

注意/2要向-∞取整(被坑的好惨啊),改:好像不用的

然后再考虑套个树链剖分,轻链和重链的第一个特殊算,对于一条连续的重链就维护除重儿子外子树最大值+=深度再取最大值,

树链剖分没有修改,所以可以预处理整条重链的答案。

复杂度: O ( n   l o g   n ) O(n~log~n) O(n log n)
当然不预处理也绰绰有余

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i <  B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define pp printf
#define ll long long
using namespace std;

const int N = 1e5 + 5;

int n, Q, fa[N], x, y;
int fi[N], nt[N * 2], to[N * 2], tot;

void link(int x, int y) {
	nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}

int p[N], q[N], tp;
int dep[N], siz[N], son[N], top[N];

void dg(int x) {
	p[x] = ++ tp;
	dep[x] = dep[fa[x]] + 1;
	siz[x] = 1;
	for(int i = fi[x]; i; i = nt[i]) if(to[i] != fa[x]) {
		fa[to[i]] = x;
		dg(to[i]), siz[x] += siz[to[i]];
		if(siz[to[i]] > siz[son[x]]) son[x] = to[i];
	}
	q[x] = tp;
}

int w[N], tw, nw[N];

void dfs(int x) {
	w[x] = ++ tw; nw[tw] = x;
	if(son[x]) top[son[x]] = top[x], dfs(son[x]);
	for(int i = fi[x]; i; i = nt[i]) if(to[i] != fa[x] && to[i] != son[x])
		top[to[i]] = to[i], dfs(to[i]);
}

int lca(int x, int y) {
	while(top[x] != top[y])
		if(dep[top[x]] >= dep[top[y]])
			x = fa[top[x]]; else y = fa[top[y]];
	return dep[x] < dep[y] ? x : y;
}

int d[N], d0, z[N], z0, e[N], e0;

int cmp(int x, int y) {
	return p[x] < p[y];
}

struct nod {
	int f1, y1, f2, y2;
	void zl() {
		f1 = f2 = 0;
		y1 = y2 = 0;
	}
	void gx(int f, int y) {
		if(f > f1) f2 = f1, y2 = y1, f1 = f, y1 = y; else
		if(f > f2) f2 = f, y2 = y;
	}
	void cs2() {
		f1 = f2 = 1e9;
		y1 = y2 = 0;	
	}
	void gx2(int f, int y) {
		if(f < f1) f2 = f1, y2 = y1, f1 = f, y1 = y; else
		if(f < f2) f2 = f, y2 = y;
	}
} f[N], g[N];

int md[N], sm[N];

void dd(int x) {
	md[x] = 0;
	for(int i = fi[x]; i; i = nt[i]) if(to[i] != fa[x]) {
		dd(to[i]);
		md[x] = max(md[x], md[to[i]] + 1);
		f[x].gx(md[to[i]] + 1, to[i]);
	}
}

void du(int x) {
	for(int i = fi[x]; i; i = nt[i]) if(to[i] != fa[x]) {
		sm[to[i]] = max(sm[x], f[x].y1 == to[i] ? f[x].f2 : f[x].f1) + 1;
		du(to[i]);
	}
}

int F[N], F1[N], F2[N];
int g1(int x, int y) { return F1[x] > F1[y] ? x : y;}
int g2(int x, int y) { return F2[x] > F2[y] ? x : y;}
int t1[N * 4], t2[N * 4];
#define i0 i + i
#define i1 i + i + 1
void bt(int i, int x, int y) {
	if(x == y) {
		t1[i] = t2[i] = nw[x];
		return;
	}
	int m = x + y >> 1;
	bt(i0, x, m); bt(i1, m + 1, y);
	t1[i] = g1(t1[i0], t1[i1]);
	t2[i] = g2(t2[i0], t2[i1]);
}
int pl, pr, px, pc;
void ft(int i, int x, int y) {
	if(y < pl || x > pr) return;
	if(x >= pl && y <= pr) {
		px = pc == 1 ? g1(px, t1[i]) : g2(px, t2[i]);
		return;
	}
	int m = x + y >> 1; ft(i0, x, m); ft(i1, m + 1, y);
}

int fq[N];

int qj(int x, int y) {
	while(1) {
		if(w[x] - w[top[x]] >= y)
			return nw[w[x] - y];
		y -= w[x] - w[top[x]] + 1;
		x = fa[top[x]];
	}
}

int la, ans;

void gg(int x, int y, int c) {
	while(top[x] != top[y]) {
		int z = f[x].y1 == la ? f[x].f2 : f[x].f1;
		if(pc == 1) z += dep[x]; else z -= dep[x];
		ans = max(ans, z + c);
		pl = w[top[x]], pr = w[x] - 1; px = 0;
		ft(1, 1, n);
		if(pc == 1) ans = max(ans, F1[px] + c); else
		ans = max(ans, F2[px] + c);
		la = top[x];
		x = fa[top[x]];
	}
		int z = f[x].y1 == la ? f[x].f2 : f[x].f1;
		if(pc == 1) z += dep[x]; else z -= dep[x];
		ans = max(ans, z + c);
		pl = w[y], pr = w[x] - 1; px = 0;
		ft(1, 1, n);
		if(pc == 1) ans = max(ans, F1[px] + c); else
		ans = max(ans, F2[px] + c);
}

multiset<int> s[N];

int main() {
	freopen("inception.in", "r", stdin);
	freopen("inception.out", "w", stdout);
	scanf("%d %d", &n, &Q);
	fo(i, 1, n - 1) {
		scanf("%d %d", &x, &y);
		link(x, y); link(y, x);
	}
	dg(1); top[1] = 1; dfs(1);
	dd(1); du(1);
	fo(i, 1, n) {
		F[i] = son[i] == f[i].y1 ? f[i].f2 : f[i].f1;
		F1[i] = F[i] + dep[i];
		F2[i] = F[i] - dep[i];
	}
	fo(i, 2, n) s[fa[i]].insert(md[i]);
	F[0] = F1[0] = F2[0] = -1e9;
	bt(1, 1, n);
	fo(ii, 1, Q) {
		ans = 0;
		scanf("%d", &z0);
		fo(i, 1, z0) scanf("%d", &z[i]);
		d0 = z0; fo(i, 1, d0) d[i] = z[i];
		sort(d + 1, d + d0 + 1, cmp);
		fo(i, 2, d0) d[++ d0] = lca(d[i - 1], d[i]);
		sort(d + 1, d + d0 + 1, cmp);
		d0 = unique(d + 1, d + d0 + 1) - (d + 1);
		e0 = 0;
		fo(i, 1, d0) {
			#define iz(x, y) (p[x] >= p[y] && p[x] <= q[y])
			while(e0 > 0 && !iz(d[i], e[e0])) e0 --;
			fq[d[i]] = e[e0]; e[++ e0] = d[i];
		}
		//dp
		fo(i, 1, d0) g[d[i]].cs2();
		fo(i, 1, z0) g[z[i]].zl();
		fd(i, d0, 1) {
			int x = d[i];
			g[fq[x]].gx2(dep[x] - dep[fq[x]] + g[x].f1, x);
		}
		fo(i, 1, d0) {
			int x = d[i];
			if(!fq[x]) continue;
			int s = g[fq[x]].y1 == x ? g[fq[x]].f2 : g[fq[x]].f1;
			g[x].gx2(dep[x] - dep[fq[x]] + s, fq[x]);
		}
		fo(i, 1, d0) {
			int x = d[i], y = fq[x];
			if(!y || fa[x] == y) continue;
			la = x;	
			int X = g[x].y1 == y ? g[x].f2 : g[x].f1;
			int Y = g[y].y1 == x ? g[y].f2 : g[y].f1;
			X ++; Y ++;
			y = qj(x, dep[x] - dep[y] - 1); x = fa[x];
			int len = dep[x] - dep[y];
			int D = (Y - X + len - 1);
			if(D < 0) D = (D - 1) / 2; else D /= 2;
			if(D > len) D = len;
			if(D >= 0) {
				int z = qj(x, D);
				pc = 2; gg(x, z, dep[x] + X);
				la = z;
			}
			if(D < 0) D = 0; else D ++;
			if(D <= len) {
				int z = fa[la];
				pc = 1; gg(z, y, -dep[y] + Y);
			}
		}	
		//out of the fake tree
		fo(i, 2, d0) {
			int x = d[i], y = fq[x];
			int z = qj(x, dep[x] - dep[y] - 1);
			s[y].erase(s[y].find(md[z]));
		}
		fo(i, 1, d0) {
			int x = d[i];
			if(s[x].size()) ans = max(ans, (*s[x].rbegin()) + 1 + g[x].f1);
		}
		fo(i, 2, d0) {
			int x = d[i], y = fq[x];
			int z = qj(x, dep[x] - dep[y] - 1);
			s[y].insert(md[z]);
		}
		
		//in the fake tree
		fo(i, 1, d0) {
			int x = d[i]; ans = max(ans, g[x].f1);
		}
		ans = max(ans, g[d[1]].f1 + sm[d[1]]);
		
		pp("%d\n", ans);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值