1. 程式人生 > 其它 >天天愛跑步題解(洛谷P1600)

天天愛跑步題解(洛谷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;
}