洛谷1600 天天爱跑步
原题链接
要解决这题有一个很重要的思想,就是将跑步的路径拆开来,分成向上走的S→LCA(S,T)S→LCA(S,T),及向下走的LCA(S,T)→TLCA(S,T)→T(S是路径起点,T是路径终点)。
然后对于两种路径单独统计贡献。
先只考虑向上走的路径S→LCA(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]=length−w[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 2018-09-28 19:59 Iowa_Battleship 阅读(157) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 聊一聊 C#异步 任务延续的三种底层玩法
· 敏捷开发:如何高效开每日站会
· 为什么 .NET8线程池 容易引发线程饥饿
· golang自带的死锁检测并非银弹
· 如何做好软件架构师
· 欧阳的2024年终总结,迷茫,重生与失业
· 史上最全的Cursor IDE教程
· 聊一聊 C#异步 任务延续的三种底层玩法
· 关于产品设计的思考
· 上位机能不能替代PLC呢?