【洛谷】1600:天天爱跑步【LCA】【开桶】【容斥】【推式子】
P1600 天天爱跑步
题目描述
小c
同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一一棵包含 n个结点和 n−1条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。
现在有m个玩家,第i个玩家的起点为 Si,终点为 Ti 。每天打卡任务开始时,所有玩家在第00秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)
小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 MB8MB 的限制。 这可能会引起函数调用层数较多时, 程序发生栈溢出崩溃。
我们可以使用一些方法修改调用栈的大小限制。 例如, 在终端中输入下列命令 ulimit -s 1048576
此命令的意义是,将调用栈的大小限制修改为 1GB。
例如,在选手目录建立如下 sample.cpp 或 sample.pas
将上述源代码编译为可执行文件 sample 后,可以在终端中运行如下命令运行该程序
./sample
如果在没有使用命令“ ulimit -s 1048576”的情况下运行该程序, sample会因为栈溢出而崩溃; 如果使用了上述命令后运行该程序,该程序则不会崩溃。
特别地, 当你打开多个终端时, 它们并不会共享该命令, 你需要分别对它们运行该命令。
请注意, 调用栈占用的空间会计入总空间占用中, 和程序其他部分占用的内存共同受到内存限制。
Solution
简直是一个心结!!!终于在考前4天解决了!!
在去年以前被誉为是noip最难的神题了QAQ(然而如今我还是这样认为)
最做不来就是树上开桶+容斥的题,现在来好好分析一下。
对于一条路径,可以求出起点和终点的LCA,将路径分为上行部分和下行部分:
然后对于上行路段的U节点,如果它想观察到这个人,那么显然$dep[u]+w[u]=dep[s]$,同理对于下行路段的V,必须要满足$dep[s]-dep[lca]+dep[v]-dep[lca]=w[v]$,化简得$w[v]-dep[v]=dis[s,t]-dep[t]$。
所以对于每个节点存在两个值$dep[u]+w[u]、w[u]-dep[u]$,对于每条路径存在两个值$dep[s]、dis[s,t]-dep[t]$,想让它们匹配起来,明显开桶即可。
遍历到u节点时,想要知道它可以观察到多少人,首先记录$ans0$,表示之前已经统计出的答案,在这个子树明显不能产生贡献,在最后统计的答案中要减去。
在回溯回来的过程中更新桶和答案,用邻接链表记录下以每个节点作为起点、终点和LCA的路径的标号,方便按照上式快速更新桶中的内容。记录下新的答案。
在最后,以u为LCA的路径就不能对它上面的节点做出贡献了,所以要把多余贡献减去。
最后还要注意,如果一条路径的LCA节点可以观察到它本身,意味着这个点计算了两次贡献,一次上行一次下行,需要减去一次。
其余细节看代码。
Code
#include<bits/stdc++.h> using namespace std;int n, m;const int A = 300000;struct Point {int s, t, len, lca; } r[300005];struct Node {int v, nex; } Edge[600005], Edge_st[600005], Edge_ed[600005], Edge_lca[600005];int h[300005], stot; void add(int u, int v) {Edge[++stot] = (Node) {v, h[u]};h[u] = stot; }int hst[300005]; void add_st(int u, int id) {Edge_st[++stot] = (Node) {id, hst[u]};hst[u] = stot; }int hed[300005]; void add_ed(int u, int id) {Edge_ed[++stot] = (Node) {id, hed[u]};hed[u] = stot; }int hlca[300005]; void add_lca(int u, int id) {Edge_lca[++stot] = (Node) {id, hlca[u]};hlca[u] = stot; }int dep[300005], jum[300005][21]; void dfs(int u, int f) {dep[u] = dep[f] + 1;jum[u][0] = f;for(int p = 1; p <= 20; p ++)jum[u][p] = jum[jum[u][p - 1]][p - 1];for(int i = h[u]; i; i = Edge[i].nex) {int v = Edge[i].v;if(v == f) continue;dfs(v, u);} }int LCA(int u, int v) {if(dep[u] < dep[v]) swap(u, v);int t = dep[u] - dep[v];for(int p = 0; t; t >>= 1, p ++)if(t & 1) u = jum[u][p];if(u == v) return u;for(int p = 20; p >= 0; p --)if(jum[u][p] != jum[v][p]) u = jum[u][p], v = jum[v][p];return jum[u][0]; }int ans0[300005], ans[300005], w[300005], t1[600005], t2[600005]; void Dfs(int u, int f) {ans0[u] = t1[dep[u] + w[u]] + t2[w[u] - dep[u] + A];///之前的贡献 不算在这个点的范围 for(int i = h[u]; i; i = Edge[i].nex) {int v = Edge[i].v;if(v == f) continue;Dfs(v, u);}//回溯过程中更新答案 for(int i = hst[u]; i; i = Edge_st[i].nex) t1[dep[u]] ++;for(int i = hed[u]; i; i = Edge_ed[i].nex) t2[r[Edge_ed[i].v].len - dep[u] + A] ++;ans[u] = t1[dep[u] + w[u]] + t2[w[u] - dep[u] + A];////新增后的贡献(包括原来的) for(int i = hlca[u]; i; i = Edge_lca[i].nex) {int id = Edge_lca[i].v;t1[dep[r[id].s]] --;t2[r[id].len - dep[r[id].t] + A] --;/////以u为lca的所有路径的贡献在回溯回去时都没用了 } }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);}dfs(1, 0);for(int i = 1; i <= n; i ++) scanf("%d", &w[i]);for(int i = 1; i <= m; i ++) {int s, t;scanf("%d%d", &s, &t);r[i].s = s, r[i].t = t;r[i].lca = LCA(s, t);r[i].len = dep[s] + dep[t] - dep[r[i].lca] * 2;}stot = 0;for(int i = 1; i <= m; i ++) add_st(r[i].s, i);stot = 0;for(int i = 1; i <= m; i ++) add_ed(r[i].t, i);stot = 0;for(int i = 1; i <= m; i ++) add_lca(r[i].lca, i);Dfs(1, 0);for(int i = 1; i <= m; i ++)if(dep[r[i].lca] + w[r[i].lca] == dep[r[i].s]) ans[r[i].lca] --;////如果lca可以观察到自己这条路,那它在两段路中多算了一次贡献 for(int i = 1; i <= n; i ++) printf("%d ", ans[i] - ans0[i]);return 0; }
转载于:https://www.cnblogs.com/wans-caesar-02111007/p/9916659.html
【洛谷】1600:天天爱跑步【LCA】【开桶】【容斥】【推式子】相关推荐
- 【洛谷P4707】重返现世【扩展Min-Max容斥】【dp】
传送门 题意:NNN种物品,每次第iii种产生概率为piM\frac{p_i}{M}Mpi,∑pi=M\sum p_i=M∑pi=M.求生成KKK种不同物品的期望时间 模998244353998 ...
- 洛谷P1450:硬币购物(背包、容斥)
解析 呜呜呜不废啊 我只会跑n遍多重背包 感觉非常神仙的一道题 之所以只是蓝的可能是因为代码实现难度太低了吧 但感觉思想真的很难想到 也可能是我太菜了 容斥相关还是需要加强啊qwq 考虑如果没有硬币个 ...
- [luogu]P1600 天天爱跑步[LCA]
[luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...
- BZOJ 4719--天天爱跑步(LCA差分)
4719: [Noip2016]天天爱跑步 Time Limit: 40 Sec Memory Limit: 512 MB Submit: 1464 Solved: 490 [Submit][St ...
- [NOIP2016]天天爱跑步(lca+乱搞)
2557. [NOIP2016]天天爱跑步 时间限制:2 s 内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑 ...
- [BZOJ4719][P1600][NOIP2016]天天爱跑步[LCA+dfs序+差分]
题意:一棵树,有 \(m\) 个人从 \(s_i\) 到 \(t_i\) 跑步,每个人的速度都是1,每个点有一个观察员 当一个人在w[i]经过第 \(i\) 个点的时候 第 \(i\) 个点的答案+1 ...
- 洛谷P2680 运输计划(倍增LCA + 树上差分 + 二分答案)
[题目链接] [思路]: 根据题意可以明显看出,当所有任务都完成时的时间是最终的结果,也就是说本题要求,求出最小的最大值. 那这样的话就暗示了将答案二分,进行check. [check方法]: 如果说 ...
- [模板]洛谷T3379 最近公共祖先(LCA) 倍增+邻接表
一年前听说的这东西...现在终于会了... 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> ...
- 洛谷P3120 [USACO15FEB]牛跳房子(动态开节点线段树)
题意 题目链接 Sol \(f[i][j]\)表示前\(i\)行\(j\)列的贡献,转移的时候枚举从哪里转移而来,复杂度\(O(n^4)\) 然后考虑每一行的贡献,动态开节点线段树维护一下每种颜色的答 ...
- 洛谷 5205 【模板】多项式开根
题目:https://www.luogu.org/problemnew/show/P5205 不会二次剩余. 牛顿迭代推开根式子: \( f^2(x)-g(x)=0 \) \( f(x)=f_0(x) ...
最新文章
- 2018-3-7 Hadoop简介1(名字的由来,以及基本的结构)
- node-mongo-服务器封装
- Spring Cloud Feign设计原理
- 树莓派与安卓手机app的WIFI通信(局域网通信)
- odoo10参考系列--Odoo中的安全机制
- hardmard积 用什么符号表示_[关于数学哲学的科普]为数学学习准备的基础数理逻辑(用自然推理系统展现的命题演算以及一阶谓词演算)...
- VC知识库BLOG-技术,非技术......
- WEB前端性能优化集合
- 数字水印技术的原理、实现及其应用
- 苹果id被禁用_【苹果ios游戏推荐】模拟人生免费版
- Codeforces Round #365 (Div. 2) C. Chris and Road(思维题-零点定理)
- 运维就是一场没有硝烟的战争
- ROS学习之路的整理
- JAVA教材(推荐新手学习)
- 饮食、生物钟、肠道菌群的“三角恋”
- 微信公众平台开发[1] —— 获取用户Openid及个人信息
- 寄云一站式平台支持起医疗大数据的构建与运营
- selenium打开chrome浏览器无痕模式
- 安全集成服务资质是什么都有哪些等级?申请安全集成服务资质认证有什么好处?
- Win10家庭中文版用批处理打开本地组策略