思维难度:cf2300+

实现方案:贡献计算

方法:线段树合并或者树上差分+桶的统计

思路点补充:

按照题目的设定,如果一个观察员 \(j\) 能够观察到某个人经过,说明 \(j\)一定在第 \(i\)个人的路径上。

树上的路径是唯一确定的,由此可以得到两种情况。

情况一:观察员 \(j\) 此时在 \(s\) 到 \(lca\) 的路径上(包括 \(lca\) )。

此时我们可以直接得到关系式:\(dep_s = dep_j + w_j\)。

情况二:观察员 \(j\) 此时在 \(t\) 到 \(lca\) 的路径上(不包括 \(lca\) )。

此时我们得到关系式:\(dis(s,t) - dep_t = w_j - dep_j\)。

公式 \(1\) :\(dep_s = dep_j + w_j\)。

公式 \(2\) :\(dis(s,t) - dep_t = w_j - dep_j\)。

观察这两个公式,我们将所有的不同的参数放在两端,此时式子等号左边是 \(s\) 到 \(t\) 的路径上的答案,右边是观察员 \(j\) 的答案。

所以我们看到上述两个公式的左边之后,$ dep_s $ 和 \(dis(s,t) - dep_t\) 都是固定值。

也就是对于观察员 \(j\) ,只要找到其子树上是否找到两个点的路径 \(s\) 到 \(t\) 是否满足公式 \(1\) 和 公式 \(2\) 即可。

但是这样实际求解的效率极低。

不妨反向考虑每条路径对于 所有观察点的影响:

观察下图,我们可以知道需要找到观察员是否对于每条路径满足上述两个公式。

用公式 \(1\) 作为样例,这个公式 \(dep_s = dep_j + w_j\) 存在的有效时间是 \(s\) 到 \(lca\) ,超过这个范围就不生效了。

相当于答案 \(dep_s\) 在 \(s\) 处出现,在 \(lca\) 处消失。

问题可以转换为 在观察员 \(j\) 这个点的子树内,有多少个点满足 \(dep_i\)等于观察员的 \(dep_j + w_j\)。

此时我们只需要将\(s\) 到 \(lca\) 上进行点权都加上 \(dep_s\) ,这样就可以得到正确的影响范围,从而将该问题转换为子树问题。

换句话来说:

公式 \(1\) 的左边 \(dep_s\) 理解为第一类特殊的数字,存在的范围是 \(s\) 到 \(lca\) , 题目统计的是每个观察点子树内有多少个存在的第一类数字。

公式 \(2\) 的右边 \(dis(s,t) - dep_t\) 理解为第二类特殊的数字,存在的范围是 \(s\) 到 \(lca\) 的直接儿子节点,题目统计的是每个观察点子树内有多少个存在的第二类数字。

两类数相加就是答案。

基本思路理清楚了,回归到子树问题本身,如何求解对应阶段的答案,为什么需要将 \(dep_s\) 这个当作第一类数当作增量将 \(s\) 到 \(lca\) 的点权都进行修改。

这是因为我们必须将影响的范围全部标记。比如在下图之中,答案对于两个观察员都有影响。

标记的做法这里我们可以利用树上点差分的方式,这里需要大家熟知差分的本质,假设有一个人从 \(s\) 移动到 \(lca\) 需要将 \(dep_s\) 的影响带给途径的所有点。

此时我们可以理解为这个人在 \(s\) 点拿到了 \(dep_s\) ,但是从 \(lca\) 之后点就不属于这个路径范围了,立马对 \(lca\) 的父亲减去 \(dep_s\)。

树上差分不在同一子树内也是同样的理解。

具体实现分析

vector<int> add1[maxn], sub1[maxn]; // 差分记录s 到 lca的影响
vector<int> add2[maxn], sub2[maxn]; // 差分记录lca子节点 到 t的影响
add1[s].push_back(dep[s]);
sub1[fa_lca].push_back(dep[s]); // 差分当前链
add2[t].ush_back(dep[s] - 2 * dep[LCA]);
sub2[LCA].push_back(dep[s] - 2 * dep[LCA]);
// 准确定位差分的每个点 到达该点了,就直接该add的add,该删除的删除
其中s到lca的差分是[s]++,[fa_lca]--
其中lca的子节点到t的差分是sub2[LCA]--,add2[t]++

最后如何统计子树答案?

使用两个全局桶,记录两种方向下,u这个观察点dep[u] + w[u]以及w[u] - dep[u] + n的出现次数
子树内的数值出现次数,直接采用前后的状态之差
比如递归到3的时候,cnt1统计的次数是5次
回溯到3的时候,cnt1变成了10次,说明这个地方出现了5条路径满足答案。

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 7;
int n, m, t, dep[maxn], dp[maxn][21];
int w[maxn];
int cnt1[maxn << 1];
int cnt2[maxn << 1];
int ans[maxn];
queue<int> q;
vector<int> add1[maxn], sub1[maxn];
vector<int> add2[maxn], sub2[maxn];
vector<int> G[maxn];
bool vis[maxn];
void add_edge(int u, int v)
{
G[u].push_back(v);
G[v].push_back(u);
return;
}
void dfs(int u, int fa) // u的父节点是fa
{
dp[u][0] = fa; // 边界条件
dep[u] = dep[fa] + 1; // 深度
for (int i = 1; (1 << i) <= dep[u]; i++)
{
dp[u][i] = dp[dp[u][i - 1]][i - 1];
}
for (int i = 0; i < G[u].size(); i++) // 循环u的相邻结点
{
int v = G[u][i];
if (v != fa)
{
dfs(v, u);
}
}
return;
}
int lca(int x, int y) // 约定y的深度更大
{
if (dep[x] > dep[y])
{
swap(x, y);
}
for (int i = 20; i >= 0; i--) // 让y往上跳与x高度一致
{
if (dep[x] <= dep[dp[y][i]])
{
y = dp[y][i]; // y跳
}
}
if (x == y)
{
return x;
}
for (int i = 20; i >= 0; i--) // x、y一起往上跳
{
if (dp[x][i] != dp[y][i])
{
x = dp[x][i];
y = dp[y][i];
}
}
return dp[x][0];
} void dfs2(int u, int fa)
{
int val1 = cnt1[dep[u] + w[u]];
int val2 = cnt2[w[u] - dep[u] + n]; // 防止负数,加一个n
for (int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if (v == fa)
{
continue;
}
dfs2(v, u);
}
for (int i = 0; i < add1[u].size(); i++)
{
cnt1[add1[u][i]]++;
}
for (int i = 0; i < sub1[u].size(); i++)
{
cnt1[sub1[u][i]]--;
}
for (int i = 0; i < add2[u].size(); i++)
{
cnt2[add2[u][i] + n]++;
}
for (int i = 0; i < sub2[u].size(); i++)
{
cnt2[sub2[u][i] + n]--;
}
ans[u] += cnt1[dep[u] + w[u]] - val1 + cnt2[w[u] - dep[u] + n] - val2; // 记录两个点的前后状态之差
return;
} int main()
{
cin >> n >> m;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
add_edge(u, v);
}
for (int i = 1; i <= n; i++)
{
cin >> w[i];
}
dfs(1, 0);
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
int LCA = lca(u, v);
int fa_lca = dp[LCA][0];
add1[u].push_back(dep[u]);
sub1[fa_lca].push_back(dep[u]); // 差分当前链
add2[v].push_back(dep[u] - 2 * dep[LCA]);
sub2[LCA].push_back(dep[u] - 2 * dep[LCA]);
}
dfs2(1, 0);
for (int i = 1; i <= n; i++)
{
cout << ans[i] << ' ';
}
return 0;
}

P1600 [NOIP 2016 提高组] 天天爱跑步解析-树上差分+全局桶的更多相关文章

  1. P1600 [NOIP2016 提高组] 天天爱跑步 (树上差分)

    对于一条路径,s-t,位于该路径上的观察员能观察到运动员当且仅当以下两种情况成立:(d[ ]表示节点深度) 1.观察员x在s-lca(s,t)上时,满足d[s]=d[x]+w[x]就能观察到,所以我们 ...

  2. ☆ [NOIp2016] 天天爱跑步 「树上差分」

    题目类型:LCA+思维 传送门:>Here< 题意:给出一棵树,有\(M\)个人在这棵树上跑步.每个人都从自己的起点\(s[i]\)跑到终点\(t[i]\),跑过一条边的时间为1秒.现在每 ...

  3. 【NOIP2016】天天爱跑步(树上差分)

    题意: 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图可以看作一一棵包含 N个结点 ...

  4. [luogu1600 noip2016] 天天爱跑步 (树上差分)

    题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵 ...

  5. [NOIP2016]天天爱跑步 题解(树上差分) (码长短跑的快)

    Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图 ...

  6. bzoj 4719: [Noip2016]天天爱跑步【树上差分+dfs】

    长久以来的心理阴影?但是其实非常简单-- 预处理出deep和每组st的lca,在这里我简单粗暴的拿树剖爆算了 然后考虑对于一组s t lca来说,被这组贡献的观察员x当且仅当: x在s到lca的路径上 ...

  7. noip 2016提高组D2T1 problem

    我们可以先预处理一下组合数模K的值,然后我们可以发现对于答案ji[n][m],可以发现递推式ji[i][j]=ji[i-1][j]+ji[i][j-1]-ji[i-1][j-1]并对于Cij是否%k等 ...

  8. noip 2016 提高组题解

    前几天写的那个纯属搞笑.(额,好吧,其实这个也不怎么正经) 就先说说day2吧: T1:这个东西应该叫做数论吧. 然而我一看到就照着样例在纸上推了大半天(然而还是没有看出来这东西是个杨辉三角) 然后就 ...

  9. noip 2016 提高组总结(不是题解)

    小弱鸡杨树辰是第一次参加像noip这样的高大上的比赛,于是他非常,非常,非常激动. 当他第二天考完试后,他正在yy自己的分数:day1T1应该是a掉了,T2写了个30分的暴力,T3也是个40分的暴力, ...

  10. NOIP 2016 提高组 复赛 Day2T1==洛谷2822 组合数问题

    题目描述 组合数表示的是从n个物品中选出m个物品的方案数.举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法.根据组合数的定 义,我们可以给出计算 ...

随机推荐

  1. 笔记 - linux子系统更换阿里云镜像源

    平时还是用 windows 多一些, 偶尔会玩一玩 linux, 之前给我一台多年的笔记本装了个 manjaro , 颜值是蛮高的, 就一点也不太熟, 就不想玩了, 还是用子系统, win 有支持 U ...

  2. 探索Rust:深入了解结构体和枚举的用途与高级功能

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  3. MySQL保姆级安装教程(附资源包+5分钟极速配置+环境变量调试技巧)

    mysql简介 MySQL 是一种开源关系型数据库管理系统(RDBMS),由瑞典 MySQL AB 公司于 1995 年开发,现由 Oracle 公司维护.它采用客户端-服务器架构,支持多线程处理和高 ...

  4. 深度探索C++对象模型--读书笔记

    深度探索C++对象模型 第一章:关于对象 封装之后的布局成本 C++在布局以及存取时间上主要的额外负担是由virtual引起 1.VIrtual function机制:用以支持一个有效率的" ...

  5. C#实现MCP Client 与 LLM 连接,抓取网页内容功能!

    前面的课程,我们已经用C#实现了,自己的MCP Client. 下面我们一起来实现,MCP Client与LLM 对接. 一.添加依赖库 目前来说,绝大部分的大模型的API,都是遵循OpenAI的接口 ...

  6. GLSL的预处理器都有哪些规定?

    GLSL的预处理器都有哪些规定? 下面的内容,英文版取自GLSLangSpec.4.60.pdf,中文版是我的翻译,只求意译准确易懂,不求直译严格匹配. 3.3. Preprocessor There ...

  7. 一文速通Python并行计算:11 Python多进程编程-进程之间的数据安全传输-基于队列和管道

    一文速通 Python 并行计算:11 Python 多进程编程-进程之间的数据安全传输-基于队列和管道 摘要: Python 多进程中,Queue 和 Pipe 提供进程间安全通信.Queue 依赖 ...

  8. NOIp2020复赛前日志

    NOIp2020复赛前日志 组合数和卢卡斯定理 首先写的顺序别搞错了 从\(n\)个不同元素中取出\(m(m≤n)\)个元素的所有组合的个数 \[C_n^m=\binom nm=C(n,m)=\fra ...

  9. Hyperledger Fabric出块配置详解

    Hyperledger Fabric的出块主要是Orderer节点负责,出块配置位于创世区块中,支持定时出块.达到一定交易数出块两种条件.出块配置位于configtx.yaml中,修改出块配置后需要重 ...

  10. `.NC`文件的读取与使用

    .NC文件的读取与使用 前言 NetCDF(network Common Data Form)网络通用数据格式是一种面向数组型并适于网络共享的数据的描述和编码标准.目前,NetCDF广泛应用于大气科学 ...