NOIP2016天天爱跑步

NOIP2016 天天爱跑步

题意:有一棵\(n\)个点的树,有\(m\)个玩家,每个玩家有一个起点\(S_i,T_i\),所有的玩家在第\(0\)秒从自己的起点出发,以每秒跑一条边的速度,沿着最短路径向终点跑。节点\(j\)的观察员会在第\(W_j\)秒观察此时该节点上的玩家,求每个观察员会观察到多少人?

题目链接

数据范围:\(1<=n,m<=3e5\)

解法:

\(LCA\) + 桶 + 差分

先预处理出每个节点的深度\(dep_i\),以及每条路径的长度\(dis_i\)(用倍增\(LCA\))。

这里我们如果考虑观察员,会发现时间复杂度很难优化。所以我们考虑每个玩家对答案的贡献。

我们先把每条路径拆成向上的一段和向下的一段(最后的时候要注意起点和终点是否会对\(LCA\)重复贡献)

向上:考虑位于节点\(j\)的观察员,当且仅当\(S_i\)位于\(j\)的子树中且\(S_i,T_i\)的LCA不在\(j\)之上时,如果\(dep_{s_i} = dep_j+W_j\),则\(S_i\)会对\(j\)产生贡献。

向下同理,式子变为\(dis_i-(dep_{T_i}-dep_j)=W_j\)时会对\(j\)产生贡献。

我们用桶记录,每次出一个节点时用桶统计答案,并减掉这个点最为\(LCA\)时对这个点以上的点的答案的影响。

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 300100
int n, m;
int w[maxn], dep[maxn], dis[maxn];
int f1[maxn], f2[maxn * 2];
int st[maxn], ed[maxn], cnt[maxn], ans[maxn];
int f[maxn][22];
int fir[maxn], nxt[maxn * 2], vv[maxn * 2];
int fir1[maxn], nxt1[maxn * 2], vv1[maxn * 2];
int fir2[maxn], nxt2[maxn * 2], vv2[maxn * 2];
int tot, tot1, tot2;
void add(int u, int v)
{
	nxt[++tot] = fir[u]; fir[u] = tot; vv[tot] = v; return;
}
void add1(int u, int v)
{
	nxt1[++tot1] = fir1[u]; fir1[u] = tot1; vv1[tot1] = v; return;
}
void add2(int u, int v)
{
	nxt2[++tot2] = fir2[u]; fir2[u] = tot2; vv2[tot2] = v; return;
}
void Deal_first(int u, int fa)
{
	dep[u] = dep[fa] + 1;
	for(int i = 0; i <= 19; i++)
    	f[u][i + 1] = f[f[u][i]][i];
	for(int i = fir[u]; i; i = nxt[i])
	{
		int v = vv[i];
		if(v == fa) continue;
		f[v][0] = u;
		Deal_first(v, u);
	}
	return;
}
int LCA(int x, int y)
{
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 20; i >= 0; i--)
	{
		if(dep[f[x][i]] >= dep[y])
		{
			x = f[x][i];
		}
		if(x == y)
		{
			return x;
		}
	}
	for(int i = 20; i >= 0; i--)
	{
		if(f[x][i] != f[y][i])
        {
        	x = f[x][i];
        	y = f[y][i];
		}
	}
	return f[x][0];
}
void dfs(int u, int fa)
{
//	printf("u = %d fa = %d\n", u, fa);
	int tmp1 = f1[w[u] + dep[u]], tmp2 = f2[w[u] - dep[u] + maxn];
	for(int i = fir[u]; i; i = nxt[i])
	{
		int v = vv[i];
		if(v == fa) continue;
		dfs(v, u);
	}
	f1[dep[u]] += cnt[u];//以u为起点
	for(int i = fir1[u]; i; i = nxt1[i])// 枚举以u为终点的情况 因为每个终点的dis值不同,故须记录一个链式前向星
	{
		int v = vv1[i];
//	    f2[dis[v] - dep[u] + maxn] += 1;
        f2[dis[v] - dep[ed[v]] + maxn] += 1;
	}
	ans[u] += (f1[w[u] + dep[u]] - tmp1) + (f2[w[u] - dep[u] + maxn] - tmp2);
	for(int i = fir2[u]; i; i = nxt2[i])
	{
		int v = vv2[i];
		f1[dep[st[v]]] -= 1;
		f2[dis[v] - dep[ed[v]] + maxn] -= 1;
	}
	return;
}
int main()
{
	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]);
	Deal_first(1, 1);  dep[1] = 1;// printf("makiah");
	for(int i = 1; i <= m; i++)
	{
		scanf("%d%d", &st[i], &ed[i]);
		int fa = LCA(st[i], ed[i]);
	//	printf("fa = %d\n", fa);
		dis[i] = dep[st[i]] + dep[ed[i]] - dep[fa] * 2;
		cnt[st[i]]++;
		add1(ed[i], i);
		add2(fa, i);
		if(dep[fa] + w[fa] == dep[st[i]]) ans[fa]--;
	}
	dfs(1, 0);
	for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
	return 0;
}
posted @ 2019-10-28 13:53  Akaina  阅读(108)  评论(0编辑  收藏  举报