送鱼

题目

传送门 to usOJ

题目描述
狉狉牛家里有一个很大的养鱼场,所以他天天摸鱼!他常常试图否认这一事实。

今天他要给他的朋友 s y \tt sy sy 送一些鱼。可是鱼是在游动的,有的时候鱼浮出水面吐泡泡,你就可以一把抓住——前提是你在这个码头;如果鱼潜入深处,你就拿它毫无办法。

狉狉牛的所有 n n n 个码头,和连接它们的 n − 1 n-1 n−1 条路,恰好形成了一个树形结构。由于 1 1 1 号码头是狉狉牛最初的码头——也就是他的摸鱼事业开始的地方——它会是树的根,不过那里的条件是最糟的。以此类推,距离 1 1 1 号码头越近,条件就越差。

s y \tt sy sy 站在某个地方时,他得通过路径移动到某个有鱼浮起的码头,然后拿一条。这条路径他可以任意选——他走不走回头路是自己的事,但是有些码头是所有路径都会经过的。这种必要的码头中条件最差的一个就是 “限制码头” 。 s y \tt sy sy 希望选择一个码头,使得 “限制码头” 的条件最好。你只要告诉她最好的 “限制码头” 是哪一个就行了!

不过鱼是在游动的, s y \tt sy sy 也会很多次地向你请教。你能帮忙吗?

最开始所有的码头都看不到鱼,唯见长江天际流。

数据范围与提示
max ⁡ ( n , q ) ≤ 8 × 1 0 5 \max(n,q)\le 8\times 10^5 max(n,q)≤8×105 。

思路壹

转化题意:操作为翻转一个点的颜色,与询问所有黑色点与当前点的 l c a \tt lca lca 中深度最大的一个。

显然直接树剖,将一个黑色点到根的路径全部标记为黑色,然后询问当前点到根上的标记点中最深的一个。

O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n) 好像不能接受?那就写 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 吧!虽然实际上可能不会快很多。

代码壹

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void getMax(int &a,const int &b){
	(a < b) ? (a = b) : 0;
}

const int MaxN = 800005;
struct Edge{
	int to, nxt;
};
Edge e[MaxN];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
	e[cntEdge].to = b;
	e[cntEdge].nxt = head[a];
	head[a] = cntEdge ++;
}

int siz[MaxN], son[MaxN];
int lsiz[MaxN]; // size of light son
void getInfo(int x){
	siz[x] = 1, son[x] = 0;
	for(int i=head[x]; ~i; i=e[i].nxt){
		getInfo(e[i].to);
		siz[x] += siz[e[i].to];
		if(siz[e[i].to] > siz[son[x]])
			son[x] = e[i].to;
	}
	lsiz[x] = siz[x]-siz[son[x]];
}

int fa[MaxN], zi[MaxN][2];
int col[MaxN]; // 自己、轻儿子中黑点个数
int all[MaxN], ys[MaxN], tot;
bool isRt[MaxN]; // 父边为虚边么
int mx[MaxN]; // 深度最大的黑点
void pushUp(int x){
	if(col[x] > 0) // 自己是黑点
		mx[x] = x; // maybe myself
	else mx[x] = 0; // init
	if(mx[zi[x][1]] != 0)
		mx[x] = mx[zi[x][1]];
	else if(mx[x] == 0)
		mx[x] = mx[zi[x][0]];
}
int build(int l,int r){
	if(l > r) return 0;
	int x = lower_bound(all+l,all+r+1,
		(all[l-1]+all[r])>>1)-all;
	zi[ys[x]][0] = build(l,x-1);
	zi[ys[x]][1] = build(x+1,r);
	x = ys[x]; // 实际的点
	return fa[zi[x][0]] =
		   fa[zi[x][1]] = x;
}
int dfs(int x){
	ys[++ tot] = x; // 映射
	all[tot] += lsiz[x];
	if(head[x] == -1){
		int rt = build(1,tot);
		tot = all[1] = 0;
		return rt; // 记得清空
	}
	all[tot+1] = all[tot];
	int rt = dfs(son[x]);
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != son[x]){
			int sy = dfs(e[i].to);
			fa[sy] = x, isRt[sy] = 1;
		}
	return rt;
}

void updata(int x,int v){
	col[x] += v; // itself
	while(fa[x] != 0){
		if(isRt[x]) col[fa[x]] += v;
		pushUp(x); x = fa[x];
	}
	pushUp(x); // 少了一次
}
int queryChain(int x){
	if(col[x] > 0 || mx[zi[x][1]])
		return x; // 自己是黑的
	int ans = mx[zi[x][0]];
	for(int i=x; !isRt[i]; i=fa[i])
		if(i == zi[fa[i]][0]){
			if(col[fa[i]] > 0
			|| mx[zi[fa[i]][1]])
				return x;
		}
		else if(!ans && col[fa[i]] > 0)
			ans = fa[i]; // 深度尽量大
		else if(!ans) // 前面得到的必然更深
			ans = mx[zi[fa[i]][0]];
	return ans;
}
int query(int x){
	int ans = queryChain(x);
	for(; fa[x]&&!ans; x=fa[x])
		if(isRt[x]) // 跳到新的链上
			ans = queryChain(fa[x]);
	return ans;
}

bool tag[MaxN];
int main(){
	int n = readint(), q = readint();
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	for(int i=2; i<=n; ++i)
		addEdge(readint(),i);
	getInfo(1), isRt[dfs(1)] = 1;
	while(q --){
		int x = readint();
		if(x < 0)
			printf("%d\n",query(-x));
		else{
			updata(x,tag[x]?-1:1);
			tag[x] = !tag[x];
		}
	}
	return 0;
}

思路贰

l c a lca lca 深度最小的一定是 d f s \tt dfs dfs 序最靠近的。所以只需要求两次 l c a lca lca 即可。

复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) ,结果倍增求 l c a lca lca 还会被卡常

上一篇:灵活填充健康和可持续食品


下一篇:牛客竞赛第六场 贪吃蛇 BFS解法与DFS解法 迷宫问题