1. 程式人生 > 其它 >題解[NOIP2016 提高組] 天天愛跑步

題解[NOIP2016 提高組] 天天愛跑步

算是NOIP中比較麻煩的題了,看題解感覺處理的很巧妙

題意就不再贅述了,剛開始的想法是遍歷列舉每一條路徑,但是無論如何這樣做的複雜度最壞都有O(nm)

所以嘗試換一種方法,從觀察員下手,對於每一個觀察員,我們只需要找到每一條路徑帶給他的貢獻

那這個貢獻怎麼求呢?

對於每一條路徑(u,v),我們都可以將它拆為 u->lca 與 lca ->v

假設觀察員的位置p在 u-> lca 上,當depth[u]+w[p]=depth[p]時才會有貢獻

而當p在 lca->v 上時,只有dist(u,v)-(depth[v]-depth[p])=w[p],即dist(u,v)-depth[v]=w[p]-depth[p]時才有貢獻

然後是統計貢獻,如果我們在統計貢獻時也一個一個去計算,那麼複雜度就又上去了,所以這個時候可以用一個桶來處理,

這也是這道題比較巧妙的一點,

先看u->lca,對於點x,我們將它貢獻的答案存在cnt[depth[x]]裡,對於這條路徑上的點p,更新答案就只需要加上cnt[depth[p]+w[p]]

對於lca->v,我們就將答案貢獻存在cnt[dist(u,v)-depth[v]]裡,同理對於p,更新答案加上cnt[w[p]-depth[p]]

然後是這道題中的一些細節部分,結合程式碼理解

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 3e5 + 5;

int cnt[N], ans[N], dist[N],t1[2 * N];
int w[N], s[N], t[N], fa[N][21], dep[N], t2[2 * N];
int head[N], ver[2 * N], net[2 * N], idx;
int head1[N], ver1[2 * N], net1[2 * N], idx1;
int head2[N], ver2[2 * N], net2[2 * N], idx2;

void add(int a, int b)
{
	net[++idx] = head[a], ver[idx] = b, head[a] = idx;
}

void add1(int a, int b)
{
	net1[++idx1] = head1[a], ver1[idx1] = b, head1[a] = idx1;
}

void add2(int a, int b)
{
	net2[++idx2] = head2[a], ver2[idx2] = b, head2[a] = idx2;
}

void dfs1(int u, int f)
{
	fa[u][0] = f, dep[u] = dep[f] + 1;
	for (int i = 1; i <= 20; i++)
		fa[u][i] = fa[fa[u][i - 1]][i - 1];	
	for (int i = head[u]; i; i = net[i])
	{
		int v = ver[i];
		if (v == f)
			continue;
		dfs1(v, u);
	}
}

int lca(int u, int v)
{
	if (dep[u] < dep[v])
		swap(u, v);
	for (int i = 20; i >= 0; i--)
		if (dep[fa[u][i]] >= dep[v])
			u = fa[u][i];
	if (u == v)
		return u;
	for (int i = 20; i >= 0; i--)
		if (fa[u][i] != fa[v][i])
			u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}//求lca的部分,不作講解

void dfs2(int u)
{
	int tp1 = t1[dep[u] + w[u]], tp2 = t2[w[u] - dep[u] + N];//先存下來,接下來有用
	for (int i = head[u]; i; i = net[i])
	{
		int v = ver[i];
		if (v == fa[u][0])
			continue;
		dfs2(v);
	}
	t1[dep[u]] += cnt[u];
	for (int i = head1[u]; i; i = net1[i])
	{
		int v = ver1[i];
		t2[dist[v] - dep[t[v]] + N]++;
	}//更新貢獻
	ans[u] += t1[dep[u] + w[u]] - tp1 + t2[w[u] - dep[u] + N] - tp2;
        //tp1,tp2是之前計算的貢獻,對於一條路徑,只有帶給以該路徑的lca為根的子樹內的點貢獻,所以減去之前已經沒有用的貢獻
	for (int i = head2[u]; i; i = net2[i])
	{
		int v = ver2[i];
		t1[dep[s[v]]]--, t2[dist[v] - dep[t[v]] + N]--;//這個點沒用了,將貢獻減去
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}
	for (int i = 1; i <= n; i++)
		scanf("%d", &w[i]);
	dfs1(1, 0);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &s[i], &t[i]);
		int u = lca(s[i], t[i]);
		dist[i] = dep[s[i]] + dep[t[i]] - 2 * dep[u];//計算路徑長度
		cnt[s[i]]++;//統計每個結點作為起點的路徑條數
		add1(t[i], i);//建立一個終點與路徑的圖
		add2(u, i);//建立一個lca與路徑的圖
		if (dep[u] + w[u] == dep[s[i]])//這裡說一下,這種情況是u,v是一條鏈的情況,這種情況u->lca與lca->v會重複計算答案,所以減一
			ans[u]--;
	}
	dfs2(1);
	for (int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	return 0;
}