一名苦逼的OIer,想成为ACMer

Iowa_Battleship

洛谷1600 天天爱跑步

原题链接

要解决这题有一个很重要的思想,就是将跑步的路径拆开来,分成向上走的SLCA(S,T)SLCA(S,T),及向下走的LCA(S,T)TLCA(S,T)TS是路径起点,T是路径终点)。
然后对于两种路径单独统计贡献。
先只考虑向上走的路径SLCA(S,T)
对于一个观测点i来说,只有满足deep[S]w[i]=deep[i]deep表示点在树中的深度)的路径才能对它产生贡献。移项得deep[S]=deep[i]+w[i],于是我们可以对树进行dfs,并统计。
用一个桶up来统计当前贡献。
当搜到第i个点时(显然它的子树都已经统计完了),先在桶里加入从该点出发的路径,即up[deep[i]]+=SUM_S[i]
然后计算能对该点产生贡献的路径数,即ans[i]+=up[deep[i]+w[i]](注意deep[i]+w[i]是可能越界的)
在退出该点时,从桶里减去向上走的路径中到达该点就不往上走的路径,即该点就是LCA(S,T),向上走路径的终点。
注意因为一棵树里有很多同一深度的点,我们统计的不一定是同一棵子树下的,所以我们可以在进入该点时记录下原始的up[deep[i]+w[i]]值,在将子树搜索完后,用新的值减去原始值即是这棵子树所产生的贡献。
而对于向下走的路径LCA(S,T)T的统计方法类似,只有满足deep[T]deep[i]=lengthw[i]length表示路径长度)的路径才能对它产生贡献。移项得deep[T]length=deep[i]w[i],于是我们可以相似的用桶在dfs中统计。
当搜到第i个点时,一样先在桶里加入终点为该点的路径(因为dfs统计是向上统计,所以要加入终点),然后统计能对该点产生贡献的路径数。
在退出该点时,从桶里减去向下走路径中起点为该点的路径,即该点是LCA(S,T)
注意deep[i]w[i]deep[T]length可能是负数,所以需要加上一个数,平移数组。
最后将重复计算的LCA的贡献减1即可。
LCA部分可用倍增,或是tarjan,这里我是用的倍增。

#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
const int N = 3e5 + 10;
const int M = N << 1;
const int K = 3e5;
struct ts {
	int x, y, L, LCA;
};
ts a[N];
int fi[N], di[M], ne[M], f[N][20], de[N], an[N], ss[N], up[N], dn[M], w[N], mdp, l, gn;
vector<int>ule[N], dmo[N], dle[N];
inline int re()
{
	int x = 0;
	char c = getchar();
	bool p = 0;
	for (; c < '0' || c > '9'; c = getchar())
		p |= c == '-';
	for (; c >= '0' && c <= '9'; c = getchar())
		x = x * 10 + c - '0';
	return p ? -x : x;
}
inline void add(int x, int y)
{
	di[++l] = y;
	ne[l] = fi[x];
	fi[x] = l;
}
inline int maxn(int x, int y)
{
	return x > y ? x : y;
}
inline void sw(int &x, int &y)
{
	int z = x;
	x = y;
	y = z;
}
void dfs(int x)
{
	int i, y;
	mdp = maxn(mdp, de[x]);
	for (i = 1; i <= gn; i++)
		f[x][i] = f[f[x][i - 1]][i - 1];
	for (i = fi[x]; i; i = ne[i])
		if ((y = di[i]) ^ f[x][0])
		{
			de[y] = de[x] + 1;
			f[y][0] = x;
			dfs(y);
		}
}
int lca(int x, int y)
{
	int i;
	if (de[x] > de[y])
		sw(x, y);
	for (i = gn; ~i; i--)
		if (de[f[y][i]] >= de[x])
			y = f[y][i];
	if (!(x ^ y))
		return x;
	for (i = gn; ~i; i--)
		if (f[x][i] ^ f[y][i])
		{
			x = f[x][i];
			y = f[y][i];
		}
	return f[x][0];
}
void dfs_up(int x)
{
	int i, y, la, nw = w[x] + de[x], si = ule[x].size();
	if (nw <= mdp)
		la = up[nw];
	for (i = fi[x]; i; i = ne[i])
		if ((y = di[i]) ^ f[x][0])
			dfs_up(y);
	up[de[x]] += ss[x];
	if (nw <= mdp)
		an[x] = up[nw] - la;
	for (i = 0; i < si; i++)
		up[ule[x][i]]--;
}
void dfs_down(int x)
{
	int i, y, la, nw = de[x] - w[x], si_1 = dmo[x].size(), si_2 = dle[x].size();
	la = dn[nw + K];
	for (i = fi[x]; i; i = ne[i])
		if ((y = di[i]) ^ f[x][0])
			dfs_down(y);
	for (i = 0; i < si_1; i++)
		dn[dmo[x][i] + K]++;
	an[x] += dn[nw + K] - la;
	for (i = 0; i < si_2; i++)
		dn[dle[x][i] + K]--;
}
int main()
{
	int i, n, m, x, y;
	n = re();
	m = re();
	gn = log2(n);
	for (i = 1; i < n; i++)
	{
		x = re();
		y = re();
		add(x, y);
		add(y, x);
	}
	de[1] = 1;
	dfs(1);
	for (i = 1; i <= n; i++)
		w[i] = re();
	for (i = 1; i <= m; i++)
	{
		a[i].x = re();
		a[i].y = re();
		ss[a[i].x]++;
		a[i].LCA = lca(a[i].x, a[i].y);
		a[i].L = de[a[i].x] + de[a[i].y] - (de[a[i].LCA] << 1);
		ule[a[i].LCA].push_back(de[a[i].x]);
		dmo[a[i].y].push_back(de[a[i].y] - a[i].L);
		dle[a[i].LCA].push_back(de[a[i].y] - a[i].L);
	}
	dfs_up(1);
	dfs_down(1);
	for (i = 1; i <= m; i++)
		if (!((de[a[i].x] - w[a[i].LCA]) ^ de[a[i].LCA]))
			an[a[i].LCA]--;
	for (i = 1; i <= n; i++)
		printf("%d ", an[i]);
	return 0;
}

posted on   Iowa_Battleship  阅读(157)  评论(0编辑  收藏  举报

编辑推荐:
· 聊一聊 C#异步 任务延续的三种底层玩法
· 敏捷开发:如何高效开每日站会
· 为什么 .NET8线程池 容易引发线程饥饿
· golang自带的死锁检测并非银弹
· 如何做好软件架构师
阅读排行:
· 欧阳的2024年终总结,迷茫,重生与失业
· 史上最全的Cursor IDE教程
· 聊一聊 C#异步 任务延续的三种底层玩法
· 关于产品设计的思考
· 上位机能不能替代PLC呢?
< 2025年1月 >
29 30 31 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 1
2 3 4 5 6 7 8

导航

统计

点击右上角即可分享
微信分享提示