洛谷 题解 P1600 【天天爱跑步】 (NOIP2016)
必须得说,这是一道难题(尤其对于我这样普及组205分的蒟蒻)
提交结果(NOIP2016 天天爱跑步):
OJ名 | 编号 | 题目 | 状态 | 分数 | 总时间 | 内存 | 代码 / 答案文件 | 提交者 | 提交时间 |
---|---|---|---|---|---|---|---|---|---|
LibreOJ | #141034 | #2359. 「NOIP2016」天天爱跑步 | Accepted | 100 | 2454 ms | 72492 KiB | C++ / 6.3 K | hkxadpall | 2018-07-28 16:12:23 |
Vijos | 5b5c3486d3d8a169f1b83bb0 | P2004 天天爱跑步 | Accepted | 100 | 5445ms | 102.0 MiB | C++ / 5.85KB | 航空信奥 LV 8 | 2018-07-28 17:16:54 |
洛谷 | R9012867 | P1600 天天爱跑步 | Accepted | 100 | 3712 ms | 102.2MB | C++ / 5.85KB | 航空信奥 | 2018-07-28 16:13:03 |
题面(by 洛谷)
题目描述
小c
同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一一棵包含 n 个结点和 n-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 1 到 n 的连续正整数。
现在有 m 个玩家,第 i 个玩家的起点为 Si ,终点为 Ti 。每天打卡任务开始时,所有玩家在第 0 秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)
小c
想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点 j 的观察员会选择在第 Wj 秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第 Wj 秒也理到达了结点 j 。 小C想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点 j 作为终点的玩家: 若他在第 Wj 秒前到达终点,则在结点 j 的观察员不能观察到该玩家;若他正好在第 Wj 秒到达终点,则在结点 j 的观察员可以观察到这个玩家。
输入输出格式
输入格式:
第一行有两个整数 n 和m 。其中 n 代表树的结点数量, 同时也是观察员的数量,m 代表玩家的数量。
接下来 n- 1 行每行两个整数 u 和 v ,表示结点 u 到结点 v 有一条边。
接下来一行 n 个整数,其中第 j 个整数为 Wj, 表示结点 j 出现观察员的时间。
接下来 m 行,每行两个整数 Si ,和 Ti ,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤Wj≤n 。
输出格式:
输出1行 n 个整数,第 j 个整数表示结点 j 的观察员可以观察到多少人。
输入输出样例
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
2 0 0 1 1 1
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5
1 2 1 0 1
说明
【样例1说明】
对于 1 号点, Wi=0 ,故只有起点为1号点的玩家才会被观察到,所以玩家 1 和玩家 2 被观察到,共有 2人被观察到。
对于 2 号点,没有玩家在第 2 秒时在此结点,共 0 人被观察到。
对于 3 号点,没有玩家在第 5 秒时在此结点,共 0 人被观察到。
对于 4 号点,玩家 1 被观察到,共 1 人被观察到。
对于 5 号点,玩家 1 被观察到,共 1 人被观察到。
对于 6 号点,玩家 3 被观察到,共 1 人被观察到。
【子任务】
每个测试点的数据规模及特点如下表所示。 提示: 数据范围的个位上的数字可以帮助判断是哪一种数据类型。
【提示】
如果你的程序需要用到较大的栈空问 (这通常意味着需要较深层数的递归), 请务必仔细阅读选手日录下的文本当rumung:/stact.p″, 以了解在最终评测时栈空问的限制与在当前工作环境下调整栈空问限制的方法。
在最终评测时,调用栈占用的空间大小不会有单独的限制,但在我们的工作环境中默认会有 8 MB 的限制。 这可能会引起函数调用层数较多时, 程序发生栈溢出崩溃。
我们可以使用一些方法修改调用栈的大小限制。 例如, 在终端中输入下列命令 ulimit -s 1048576
此命令的意义是,将调用栈的大小限制修改为 1 GB 。
例如,在选手目录建立如下 sample.cpp 或 sample.pas
将上述源代码编译为可执行文件 sample 后,可以在终端中运行如下命令运行该程序
./sample
如果在没有使用命令“ ulimit -s 1048576”的情况下运行该程序, sample会因为栈溢出而崩溃; 如果使用了上述命令后运行该程序,该程序则不会崩溃。
特别地, 当你打开多个终端时, 它们并不会共享该命令, 你需要分别对它们运行该命令。
请注意, 调用栈占用的空间会计入总空间占用中, 和程序其他部分占用的内存共同受到内存限制。
正解:
LCA+桶+树上差分(也不能说是差分但又和差分类似)
在说正解之前,先声明一些变量:
- lcafrom [x]: 以x为LCA的起点集合。
- tofrom [x]: 以x为终点的起点集合。
- lcato [x]: 以x为LCA的终点集合。
- roadcount[x]: 以x为起点的路径条数。
另外,请记住:
正解并不是对一个个玩家进行操作,而是先对全部玩家进行一些预处理,然后用两个类似的dfs函数对整棵树处理,最后再做一些微调,就输出答案。
对于玩家在树上的路径(u,v)
我们可以对其进行拆分。
拆分成: u ---> LCA(u,v) 与 LCA(u,v) ---> v 两条路径。
对于这一步,因为我们在一开始已经说明是先对每个玩家进行预处理,
所以在这一步我们选择Tarjan版本的LCA会更好一些,因为时间复杂度会更少,
不过,用倍增求LCA对于本题来说也是不会卡的(我自己在洛谷上时间最长的一个点是0.7s左右)。
我们先考虑 u ---> LCA(u,v) 这条路径,这是一条向“上”跑的路径。
对与这条路径上的点i来说,当且仅当deep[i]+w[i] = deep[u]时,u节点对i节点是有贡献的。
那么也就是说,只要符合deep[i]+w[i]的全部是玩家起点的点,就能对i点产生贡献。
在叙述完向上的路径后,我们再来考虑向下的路径,即LCA(u,v) --->v。
对于向下走的路径,我们也思考,在什么条件下,这条路径上的点会获得贡献呢?
很明显的,当 dis(u,v)-deep[v] = w[i]-deep[i] 等式成立的时候,这条路径将会对i点有贡献。
所以,对于这道题来说,现在我们主要的思路已经完全讲完了。
但是,对于实现来说,需要注意以下几点。
- 对于桶bucket来说,我们在计算的过程中其下标可能是负值,所以我们在操作桶时要将其下标右移 MAXN 即点数。
- 如果一条路径的LCA能观察到这条路上的人,我们还需将该LCA去重。
条件是: if(deep[u] == deep[lca]+w[i])ans[lca]--;
下面贴下代码(LCA用的是倍增)
/*
Problem: P1600 天天爱跑步
Author: 航空信奥
Date: 2018/07/28
Description: 一个恶心的,困难的问题,咩~咩~咩~~~
*/ // 注:个人习惯,数组元素下标一般从1开始
#pragma GCC optimize("O1")
#pragma GCC optimize("O2")
#pragma GCC optimize("O3")
// 不要管我,我就要皮,o1o2o3齐开,咩~~~
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <vector>
#include <map>
#define Char_Int(a) ((a) & 15)
#define Int_Char(a) ((a) + '0')
#define rg register namespace hkxa {
template <typename _TpInt> inline _TpInt read();
template <typename _TpInt> inline void write(_TpInt x);
template <typename _TpSwap> inline void swap(_TpSwap &x, _TpSwap &y); # define SizeN
# define SizeLogN
# define tong(a) bucket[a + SizeN] int n, m;
int bucket[SizeN * ];
/* 使用 tong(a) 来读取桶 bucket 里面的数据,防止越界,因为在数组“桶”的使
用过程中,下标有可能成为负数。 */
std::vector<int> lcafrom[SizeN * ], tofrom[SizeN * ], lcato[SizeN * ];
int roadcount[SizeN * ];
/* lcafrom [x]: 以x为LCA的起点集合
tofrom [x]: 以x为终点的起点集合
lcato [x]: 以x为LCA的终点集合
roadcount[x]: 以x为起点的路径条数 */
int w[SizeN], ans[SizeN];
/* w [x]: 观察员x观察的时间节点
ans[x]: 观察员x观察到的玩家人数 */
int f[SizeN][SizeLogN + ], deep[SizeN], dist[SizeN];
bool use[SizeN] = {};
/* LCA用品 : f 程序算法用品 : deep, dist
f : 不用说了吧,大家都懂,倍增求LCA的父亲数组
deep : 每个节点的深度
dist : 距离 */ struct EDGE { // 存储边信息
int e;
// 通往e的道路
EDGE *next_edge;
// 下一条边的指针
EDGE() : e(), next_edge(NULL) {}
// 初始化
} *v[SizeN], edges[SizeN * ];
int ct_edges = ;
/* v数组存储了每个点最后的连接的边(指针),edges数组是树上边的集合,ct_edges
是edges的top(即:上次在edges数组的ct_edges位置添加了边) */ struct Person {
int s, t;
int lca;
int dis;
} p[SizeN];
/* 存储人的信息
s, t: 如题意
lca : s, t的公共祖先LCA
dis : s, t之间的最短路距离 */ inline void Add_edge(int s, int e) // 添加一条从s到e的边
{
ct_edges++;
edges[ct_edges].next_edge = v[s];
v[s] = edges + ct_edges; // 地址赋值
v[s]->e = e;
} inline void link(int x, int y) // 添加一条x与y之间的双向边
{
Add_edge(x, y);
Add_edge(y, x);
} void dfs_LCA(int now, int depth) // 倍增求LCA的预处理函数
{
use[now] = true;
deep[now] = depth;
for (rg int k = ; k <= SizeLogN; k++){
int j = f[now][k - ];
f[now][k] = f[j][k - ];
}
for (rg EDGE *nxt = v[now]; nxt; nxt = nxt->next_edge) {
/* 这一行等价于 for (EDGE *nxt = v[now]; nxt != NULL; nxt = nxt->next),
C++里非0为真(NULL = 0) */
if(!use[nxt->e]) {
f[nxt->e][] = now;
dist[nxt->e] = dist[now] + ;
dfs_LCA(nxt->e, depth + );
}
}
use[now] = false;
} inline int jump(int u, int depth) {
for (rg int k = ; k <= SizeLogN; k++) {
if ((depth & ( << k))) u = f[u][k];
}
return u;
} inline int LCA(int u, int v){
if (deep[u] < deep[v]) swap(u, v);
u = jump(u, deep[u] - deep[v]);
for (rg int k = SizeLogN; k >= ; k--) {
if (f[u][k] != f[v][k]) u = f[u][k], v = f[v][k]; // 倍增,一跃而上
}
return u == v ? u : f[u][];
} inline void dfs_fromLCA(int now) // 从from到LCA的路线
{
use[now] = true; // 打上tag
int prev = tong(deep[now] + w[now]);
for (rg EDGE *g = v[now]; g; g = g->next_edge) {
if (!use[g->e]) dfs_fromLCA(g->e);
}
tong(deep[now]) += roadcount[now];
ans[now] += tong(deep[now] + w[now]) - prev;
int len = lcafrom[now].size();
for (rg int k = ; k < len; k++) {
tong(deep[lcafrom[now][k]])--;
}
use[now] = false; // 删除tag
} inline void dfs_LCAto(int now) // 从LCA到to的路线
{
use[now] = true; // 打上tag
int prev = tong(w[now] - deep[now]);
for (rg EDGE *g = v[now]; g; g = g->next_edge) {
if(!use[g->e]) dfs_LCAto(g->e);
}
int len = tofrom[now].size();
for (rg int k = ; k < len; k++) {
tong(tofrom[now][k])++;
}
ans[now] += tong(w[now] - deep[now]) - prev;
len = lcato[now].size();
for (rg int k = ; k < len; k++) {
tong(lcato[now][k])--;
}
use[now] = false;
} inline int main()
{
n = read<int>();
m = read<int>();
for (rg int i = ; i <= n - ; i++) {
link(read<int>(), read<int>());
}
for (rg int i = ; i <= n; i++)
w[i] = read<int>();
f[][] = ;
dfs_LCA(, ); // 倍增求LCA的预处理
int S, T;
for(rg int i = ; i <= m; i++) { // 核心算法之预处理
S = read<int>();
T = read<int>();
p[i].s = S;
p[i].t = T;
p[i].lca = LCA(S, T);
p[i].dis = dist[S] + dist[T] - dist[p[i].lca] * ;
roadcount[S]++;
lcafrom[p[i].lca].push_back(S);
tofrom[T].push_back(p[i].dis - deep[T]);
lcato[p[i].lca].push_back(p[i].dis - deep[T]);
}
// 核心算法 - 开始
dfs_fromLCA(); // 从下至上(从from到LCA)
dfs_LCAto(); // 从上至下(从LCA到to)
for (rg int i = ; i <= m; i++) {
if(deep[p[i].s] == deep[p[i].lca] + w[p[i].lca]) {
ans[p[i].lca]--;
}
}
for (rg int i = ; i <= n; i++) {
write<int>(ans[i]);
putchar();
}
return ;
} template <typename _TpInt>
inline _TpInt read()
{
register _TpInt flag = ;
register char c = getchar();
while ((c > '' || c < '') && c != '-')
c = getchar();
if (c == '-') flag = -, c = getchar();
register _TpInt init = Char_Int(c);
while ((c = getchar()) <= '' && c >= '')
init = (init << ) + (init << ) + Char_Int(c);
return init * flag;
} template <typename _TpInt>
inline void write(_TpInt x)
{
if (x < ) {
putchar('-');
write<_TpInt>(~x + );
}
else {
if (x > ) write<_TpInt>(x / );
putchar(Int_Char(x % ));
}
} template <typename _TpSwap>
inline void swap(_TpSwap &x, _TpSwap &y)
{
_TpSwap t = x;
x = y;
y = t;
}
} int main()
{
// system("ulimit -s 1048576");
hkxa::main();
return ;
}
天天爱跑步 - 洛谷AC代码
完美结束。
我是不会告诉你我调了6个小时的!!!
洛谷 题解 P1600 【天天爱跑步】 (NOIP2016)的更多相关文章
- 洛谷 题解 P1353 【[USACO08JAN]跑步Running】
动态规划 状态 dp[i][j]表示第i分钟疲劳值为j的最大值 初始 全部都为一个最小值"0" 转移 考虑休息和走 如果当前疲劳值比时间要大,显然不可能出现这种情况 如果比时间小 ...
- luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)
阅读体验: https://zybuluo.com/Junlier/note/1303550 为什么这一篇的Markdown炸了? # 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席 ...
- [NOIP 2016D2T2/Luogu P1600] 天天爱跑步 (LCA+差分)
待填坑 Code //Luogu P1600 天天爱跑步 //Apr,4th,2018 //树上差分+LCA #include<iostream> #include<cstdio&g ...
- 洛谷P1600 天天爱跑步(线段树合并)
小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nn ...
- [luogu]P1600 天天爱跑步[LCA]
[luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...
- 洛谷 题解 UVA572 【油田 Oil Deposits】
这是我在洛谷上的第一篇题解!!!!!!!! 这个其实很简单的 我是一只卡在了结束条件这里所以一直听取WA声一片,详细解释代码里见 #include<iostream> #include&l ...
- P1600 天天爱跑步[桶+LCA+树上差分]
题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵 ...
- 天天爱跑步 [NOIP2016]
Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务.这个游戏的地图可 ...
- 洛谷题解P4314CPU监控--线段树
题目链接 https://www.luogu.org/problemnew/show/P4314 https://www.lydsy.com/JudgeOnline/problem.php?id=30 ...
随机推荐
- DNS服务反向解析及过程中一些小问题解决
在此需要了解一下,反向解析的作用是根据IP地址查找到对应的主机名(域名),在区域文件(named.rfc1912.zones)中默认已存在一些注释内容与区域信息,可不需要删除上面实验及默认区域信息,直 ...
- 【algo&ds】2.线性表
1.线性表 线性表(英语:Linear List)是由n(n≥0)个数据元素(结点)a[0],a[1],a[2]-,a[n-1]组成的有限序列. 其中: 数据元素的个数n定义为表的长度 = " ...
- 基于Docker的Mysql主从复制
基于Docker的Mysql主从复制搭建 为什么基于Docker搭建? 资源有限 虚拟机搭建对机器配置有要求,并且安装mysql步骤繁琐 一台机器上可以运行多个Docker容器 Docker容器之间相 ...
- VS链接文件设置
右键点击文件夹,添加现有项,选中文件,添加为链接 ,点击确定,那么在修改源文件后这个目录的文件也会同步修改.如果更改源文件目录,就需要重新指定一次链接.
- C#怎么实现文件下载功能的四种方法
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Secu ...
- mysql中 drop、truncate和delete的区别
mysql中drop.truncate和delete的区别 (1)DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作. TRUNC ...
- a 标签添加 onclick 事件
a 标签添加 onclick 事件 <a href="javascript:void(0);" οnclick="js_method()">点击&l ...
- pat 1132 Cut Integer(20 分)
1132 Cut Integer(20 分) Cutting an integer means to cut a K digits lone integer Z into two integers o ...
- HTTP,HTTPS,HTTP2笔记
HTTP 网络协议分层 应用层 -> HTTP FTP 为应用软件提供了很多服务 构建于TCP协议之上 屏蔽网络传输的相关细节 传输层 -> TCP UDP 向用户提供可靠的端对端的服务( ...
- Install python3
wget https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz tar xf Python-3.7.4.tgz cd Python-3.7. ...