【洛谷】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的观察员可以观察到多少人。
输入输出样例
说明
【样例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 = ; struct Point {
int s, t, len, lca;
} r[]; struct Node {
int v, nex;
} Edge[], Edge_st[], Edge_ed[], Edge_lca[]; int h[], stot;
void add(int u, int v) {
Edge[++stot] = (Node) {v, h[u]};
h[u] = stot;
} int hst[];
void add_st(int u, int id) {
Edge_st[++stot] = (Node) {id, hst[u]};
hst[u] = stot;
} int hed[];
void add_ed(int u, int id) {
Edge_ed[++stot] = (Node) {id, hed[u]};
hed[u] = stot;
} int hlca[];
void add_lca(int u, int id) {
Edge_lca[++stot] = (Node) {id, hlca[u]};
hlca[u] = stot;
} int dep[], jum[][];
void dfs(int u, int f) {
dep[u] = dep[f] + ;
jum[u][] = f;
for(int p = ; p <= ; p ++)
jum[u][p] = jum[jum[u][p - ]][p - ];
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 = ; t; t >>= , p ++)
if(t & ) u = jum[u][p];
if(u == v) return u;
for(int p = ; p >= ; p --)
if(jum[u][p] != jum[v][p]) u = jum[u][p], v = jum[v][p];
return jum[u][];
} int ans0[], ans[], w[], t1[], t2[];
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 = ; i < n; i ++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
dfs(, );
for(int i = ; i <= n; i ++) scanf("%d", &w[i]);
for(int i = ; 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] * ;
}
stot = ;
for(int i = ; i <= m; i ++) add_st(r[i].s, i);
stot = ;
for(int i = ; i <= m; i ++) add_ed(r[i].t, i);
stot = ;
for(int i = ; i <= m; i ++) add_lca(r[i].lca, i);
Dfs(, );
for(int i = ; i <= m; i ++)
if(dep[r[i].lca] + w[r[i].lca] == dep[r[i].s]) ans[r[i].lca] --;////如果lca可以观察到自己这条路,那它在两段路中多算了一次贡献
for(int i = ; i <= n; i ++) printf("%d ", ans[i] - ans0[i]);
return ;
}
【洛谷】1600:天天爱跑步【LCA】【开桶】【容斥】【推式子】的更多相关文章
- 洛谷 P7360 -「JZOI-1」红包(Min-Max 容斥+推式子)
洛谷题面传送门 hot tea. 首先注意到这个 \(\text{lcm}\) 特别棘手,并且这里的 \(k\) 大得离谱,我们也没办法直接枚举每个质因子的贡献来计算答案.不过考虑到如果我们把这里的 ...
- 洛谷P1600 天天爱跑步(线段树合并)
小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nn ...
- 洛谷 P6295 - 有标号 DAG 计数(生成函数+容斥+NTT)
洛谷题面传送门 看到图计数的题就条件反射地认为是不可做题并点开了题解--实际上这题以我现在的水平还是有可能能独立解决的( 首先连通这个条件有点棘手,我们尝试把它去掉.考虑这题的套路,我们设 \(f_n ...
- 洛谷P2522 [HAOI2011]Problem b (莫比乌斯反演+容斥)
题意:求$\sum_{i=a}^{b}\sum_{j=c}^{d}[gcd(i,j)==k]$(1<=a,b,c,d,k<=50000). 是洛谷P3455 [POI2007]ZAP-Qu ...
- 洛谷 P1600 天天爱跑步(LCA+乱搞)
传送门 我们把每一条路径拆成$u->lca$和$lca->v$的路径 先考虑$u->lca$,如果这条路径会对路径上的某一个点产生贡献,那么满足$dep[u]-dep[x]=w[x] ...
- 洛谷P1600 天天爱跑步(差分 LCA 桶)
题意 题目链接 Sol 一步一步的来考虑 \(25 \%\):直接\(O(nm)\)的暴力 链的情况:维护两个差分数组,分别表示从左向右和从右向左的贡献, \(S_i = 1\):统计每个点的子树内有 ...
- 洛谷$P1600$ 天天爱跑步 树上差分
正解:树上差分 解题报告: 传送门$QwQ$! 这题还挺妙的,,,我想了半天才会$kk$ 首先对一条链$S-T$,考虑先将它拆成$S-LCA$和$LCA-T$,分别做.因为总体上来说差不多接下来我就只 ...
- 洛谷 P1600 天天爱跑步
https://www.luogu.org/problemnew/show/P1600 (仅做记录) 自己的假方法: 每一次跑从a到b:设l=lca(a,b)对于以下产生贡献: a到l的链上所有的点( ...
- 洛谷P1600 天天爱跑步
天天放毒... 首先介绍一个树上差分. 每次进入的时候记录贡献,跟出来的时候的差值就是子树贡献. 然后就可以做了. 发现考虑每个人的贡献有困难. 于是考虑每个观察员的答案. 把路径拆成两条,以lca分 ...
- 洛谷P1600 天天爱跑步——树上差分
题目:https://www.luogu.org/problemnew/show/P1600 看博客:https://blog.csdn.net/clove_unique/article/detail ...
随机推荐
- 写好shell脚本的13个技巧【转】
有多少次,你运行./script.sh,然后输出一些东西,但却不知道它刚刚都做了些什么.这是一种很糟糕的脚本用户体验.我将在这篇文章中介绍如何写出具有良好开发者体验的 shell 脚本. 产品的最终用 ...
- 关注网页的更新状况,了解最新的handsup 消息.
// 第一部分是网页截图和源码保存 // upon page load. var fs = require("fs"); var resourceWait = 300, maxRe ...
- 使用 Virtual Machine Manager 管理虚拟机
转载自https://www.ibm.com/developerworks/cn/cloud/library/cl-managingvms/ 尽管服务器管理在过去问题重重,但虚拟化管理简化了一些问 ...
- (记录合并)union和union all的区别
SQL UNION 操作符 UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION内部的SELECT语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每条SE ...
- C++经典面试题(最全,面中率最高)
C++经典面试题(最全,面中率最高) 1.new.delete.malloc.free关系 delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数.malloc与fre ...
- ubuntu 14.04 上配置vlc组播源
VLC: Video LAN多媒体播放器,是一个跨平台开源的软件,支持主流的编码格式MPEG-2.H.264等. (1)ubuntu上安装vlc: sudo apt-get install vlc ...
- centos6.5系统bash损坏之救援模式修复
1.模拟bash被损坏的情况 # mv /bin/bash /tmp [root@localhost ~]# sync [root@localhost ~]# shutdown -r now 2.挂载 ...
- Tpcc-MySQL对mysql数据库进行性能测试报告、分析及使用gnuplot生成图表展示
TPC-C是专门针对联机交易处理系统(OLTP系统)的规范,一般情况下我们也把这类系统称为业务处理系统. tpcc-mysql是percona基于TPC-C(下面简写成TPCC)衍生出来的产品,专用于 ...
- Android数据存储:SQLite
Android数据存储之SQLite SQLite:Android提供的一个标准的数据库,支持SQL语句.用来处理数据量较大的数据.△ SQLite特征:1.轻量性2.独立性3.隔离性4.跨平台性5. ...
- 最全Kafka 设计与原理详解【2017.9全新】
一.Kafka简介 1.1 背景历史 当今社会各种应用系统诸如商业.社交.搜索.浏览等像信息工厂一样不断的生产出各种信息,在大数据时代,我们面临如下几个挑战: 如何收集这些巨大的信息 如何分析它 如何 ...