Loading

天天爱跑步题解(洛谷P1600)

洛谷P1600 [NOIP2016 提高组] 天天爱跑步

一、大致分析

数据范围中有提示树可能退化为一条链,所以模拟时间复杂度可以达到\(O(nm)\),显然过不了。

继续分析题目,可以发现对于一条从\(s_i\)\(t_i\)的路径可以\(s_i\)\(t_i\)的最近公共祖先\(lca_i\)分为两段对于其上某点符合要求的\(k\),有:

\(k\)在上行段,则\(deep_k + w_k = deep_{s_i}\)

\(k\)在下行段,则\(dist_{{s_i},{t_i}} - w_k = deep_{t_i} - deep_k\),即\(dist_{{s_i},{t_i}} - deep_{t_i} = w_k - deep_k\)

除了以上两个式子外,又注意到能对\(k\)产生贡献的路径其起点及终点必然在以\(k\)为根的子树上,所以可以开两个数组,并在深度优先遍历时以上式左侧的方式记录,并以右式的方式统计。

但此处需要注意,\(k\)被经过的次数并不是向下递归直接通过数组查询,而是递归完子树后的值与进入子树之前的值之差。

二、代码&一些细节

1.输入:

普通的读入,不专门放代码了。

2.求LCA:

int fa[MAXN], siz[MAXN], son[MAXN], dep[MAXN] = {0}, top[MAXN];
void setup(int x, int fat)
{
	fa[x] = fat;
	siz[x] = 1, son[x] = -1;
	dep[x] = dep[fat] + 1;
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y == fat)
			continue;
		setup(y, x);
		siz[x] += siz[y];
		if (son[x] == -1 || siz[son[x]] < siz[y])
			son[x] = y;
	}
}
void cut(int x, int t)
{
	top[x] = t;
	if (son[x] == -1)
		return ;
	cut(son[x], t);
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y != fa[x] && y != son[x])
			cut(y, y);
	}
}
int getLca(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;
}

比较模版的树剖求LCA (不想开二维数组写倍增,其他的不会),不解释了。

3.预处理

void add1(int x, int y)
{
	e1[++tot1].to = y;
	e1[tot1].nxt = head1[x];
	head1[x] = tot1;
}
void add2(int x, int y)
{
	e2[++tot2].to = y;
	e2[tot2].nxt = head2[x];
	head2[x] = tot2;
}
int main()
{
	input();
	setup(1, 0);
	cut(1, 1);
	for (int i = 1; i <= m; i++)
	{
		int lca = getLca(s[i], t[i]);
		dist[i] = dep[s[i]] + dep[t[i]] - 2 * dep[lca];
		st[s[i]]++;
		add1(t[i], i);
		add2(lca, i);
		if (dep[lca] + w[lca] == dep[s[i]])
			ans[lca]--;
	}
	return 0;
}

\(dist_i\):第i条路径的长度

\(st_i\):以i为起点的路径的数量

\(add1\):以链式前向星存储以i为开头的路径的集合

\(add2\):以链式前向星存储以i为起点及终点的最近公共祖先的路径的集合

需要注意的是,若路径的起点或终点与LCA重合,且此处符合要求,则会被统计两次,因此提前减去。

if (dep[lca] + w[lca] == dep[s[i]]) ans[lca]--;

4.最后一次深搜统计答案

int bu1[MAXN * 2], bu2[MAXN * 2];
void dfs(int x)
{
	int t1 = bu1[w[x] + dep[x]], t2 = bu2[w[x] - dep[x] + MAXN];//记录进入子树前数组状态
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y != fa[x])
			dfs(y);
	}
	bu1[dep[x]] += st[x];//上行段答案
	for (int i = head1[x]; ~i; i = e1[i].nxt)//下行段答案
	{
		int y = e1[i].to;
		bu2[dist[y] - dep[t[y]] + MAXN]++;
	}
	ans[x] += bu1[w[x] + dep[x]] - t1 + bu2[w[x] - dep[x] + MAXN] - t2;//统计答案
	for (int i = head2[x]; ~i; i = e2[i].nxt)//以当前x为LCA的路径不会 对其祖宗节点和兄弟节点产生贡献
	{
		int y = e2[i].to;
		bu1[dep[s[y]]]--;
		bu2[dist[y] - dep[t[y]] + MAXN]--;
	}
}

\(bu1\):用以存储上行段的贡献

\(bu2\):用以存储下行段的贡献,\(dist_{{s_i},{t_i}} - deep_{t_i}\)可能小于\(0\),故加上\(MAXN\)

5.输出:

没什么可说。

6.最终个人代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAXN = 3e5;
struct Edge
{
	int to, nxt;
} e[MAXN * 2], e1[MAXN], e2[MAXN];
int n, m, w[MAXN], s[MAXN], t[MAXN], tot = -1, head[MAXN];
int fa[MAXN], siz[MAXN], son[MAXN], dep[MAXN] = {0};
int dfn[MAXN], rnk[MAXN], top[MAXN], cnt = 0;
int dist[MAXN], st[MAXN] = {0}, ans[MAXN] = {0};
int tot1 = -1, tot2 = -1, head1[MAXN], head2[MAXN];
int bu1[MAXN * 2], bu2[MAXN * 2];
void add(int x, int y)
{
	e[++tot].to = y;
	e[tot].nxt = head[x];
	head[x] = tot;
}
void add1(int x, int y)
{
	e1[++tot1].to = y;
	e1[tot1].nxt = head1[x];
	head1[x] = tot1;
}
void add2(int x, int y)
{
	e2[++tot2].to = y;
	e2[tot2].nxt = head2[x];
	head2[x] = tot2;
}
void input()
{
	memset(head, -1, sizeof(head));
	memset(head1, -1, sizeof(head1));
	memset(head2, -1, sizeof(head2));
	scanf("%d %d", &n, &m);
	for (int i = 1; i < n; i++)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		add(x, y);
		add(y, x);
	}
	for (int i = 1; i <= n; i++)
		scanf("%d", w + i);
	for (int i = 1; i <= m; i++)
		scanf("%d %d", s + i, t + i);
}
void setup(int x, int fat)
{
	fa[x] = fat;
	siz[x] = 1, son[x] = -1;
	dep[x] = dep[fat] + 1;
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y == fat)
			continue;
		setup(y, x);
		siz[x] += siz[y];
		if (son[x] == -1 || siz[son[x]] < siz[y])
			son[x] = y;
	}
}
void cut(int x, int t)
{
	top[x] = t;
	dfn[x] = ++cnt;
	rnk[dfn[x]] = x;
	if (son[x] == -1)
		return ;
	cut(son[x], t);
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y != fa[x] && y != son[x])
			cut(y, y);
	}
}
int getLca(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;
}
void dfs(int x)
{
	int t1 = bu1[w[x] + dep[x]], t2 = bu2[w[x] - dep[x] + MAXN];
	for (int i = head[x]; ~i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y != fa[x])
			dfs(y);
	}
	bu1[dep[x]] += st[x];
	for (int i = head1[x]; ~i; i = e1[i].nxt)
	{
		int y = e1[i].to;
		bu2[dist[y] - dep[t[y]] + MAXN]++;
	}
	ans[x] += bu1[w[x] + dep[x]] - t1 + bu2[w[x] - dep[x] + MAXN] - t2;
	for (int i = head2[x]; ~i; i = e2[i].nxt)
	{
		int y = e2[i].to;
		bu1[dep[s[y]]]--;
		bu2[dist[y] - dep[t[y]] + MAXN]--;
	}
}
int main()
{
	input();
	setup(1, 0);
	cut(1, 1);
	for (int i = 1; i <= m; i++)
	{
		int lca = getLca(s[i], t[i]);
		dist[i] = dep[s[i]] + dep[t[i]] - 2 * dep[lca];
		st[s[i]]++;
		add1(t[i], i);
		add2(lca, i);
		if (dep[lca] + w[lca] == dep[s[i]])
			ans[lca]--;
	}
	dfs(1);
	for (int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	printf("\n");
	return 0;
}
posted @ 2021-11-02 18:09  complexor  阅读(260)  评论(0编辑  收藏  举报