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