初见安~这里是传送门:洛谷P1600

题目描述

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

这个游戏的地图可以看作一一棵包含 nn个结点和 n-1n−1条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从11到nn的连续正整数。

现在有mm个玩家,第ii个玩家的起点为 S_iSi​,终点为 T_iTi​ 。每天打卡任务开始时,所有玩家在第00秒同时从自己的起点出发, 以每秒跑一条边的速度, 不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)

小c想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点jj的观察员会选择在第W_jWj​秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第W_jWj​秒也理到达了结点 jj 。 小C想知道每个观察员会观察到多少人?

注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点jj作为终点的玩家: 若他在第W_jWj​秒前到达终点,则在结点jj的观察员不能观察到该玩家;若他正好在第W_jWj​秒到达终点,则在结点jj的观察员可以观察到这个玩家。

输入格式:

第一行有两个整数nn和mm 。其中nn代表树的结点数量, 同时也是观察员的数量, mm代表玩家的数量。

接下来 n- 1n−1行每行两个整数uu和 vv,表示结点 uu到结点 vv有一条边。

接下来一行 nn个整数,其中第jj个整数为W_jWj​ , 表示结点jj出现观察员的时间。

接下来 mm行,每行两个整数S_iSi​,和T_iTi​,表示一个玩家的起点和终点。

对于所有的数据,保证1\leq S_i,T_i\leq n, 0\leq W_j\leq n1≤Si​,Ti​≤n,0≤Wj​≤n 。

输出格式:

输出1行 nn个整数,第jj个整数表示结点jj的观察员可以观察到多少人。

输入样例#1:

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

输出样例#1:

2 0 0 1 1 1 

输入样例#2:

5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5

输出样例#2:

1 2 1 0 1 

说明

【样例1说明】

对于11号点,W_i=0Wi​=0,故只有起点为1号点的玩家才会被观察到,所以玩家11和玩家22被观察到,共有22人被观察到。

对于22号点,没有玩家在第22秒时在此结点,共00人被观察到。

对于33号点,没有玩家在第55秒时在此结点,共00人被观察到。

对于44号点,玩家11被观察到,共11人被观察到。

对于55号点,玩家11被观察到,共11人被观察到。

对于66号点,玩家33被观察到,共11人被观察到。

【子任务】

每个测试点的数据规模及特点如下表所示。 提示: 数据范围的个位上的数字可以帮助判断是哪一种数据类型。

题解:

首先声明,这个题本蒟蒻不会做,看题解+写出代码共消耗了我一整天除去整个下午的时间。所以这个思路并不是我的~

题意很简单——每个点有一个时间,问刚好在这一时刻出现在这一节点的人数是多少。

看到路径是肯定要求LCA的。我们继续看——如果具体看每一条路径,那么复杂度就可以很高。所以我们可以考虑把路拆分成两条链。假设这条路是从u->v,那么我们可以将其拆分为u->lca和lca->v两条链。

那么怎么处理呢?在u->lca这条路上,易得:如果有,那么这条路径上的人一定可以在lca处被看到,对答案有贡献。而对于在lca->v这条路上,同理易得。所以我们要求的就是在路径上所有满足上面两式【lca替换为i】的点并打上标记。

如果公式没有看明白的话,我们可以画图再来理解一下【确定理解了的自行跳过】

如图,我们把从u->v的路径断开为u->lca和lca->v。在u->lca这条路上的点,只要是,也就是距离刚好是i点观测的时间,那么在点i出发的点都可以被看到;同理,在lca->v路径上,,如果有,那么同样可以被看到。为了计算方便,我们带入化简可得:,再移向可得上方式子:

然后我们就可以考虑具体实现操作了。

每走到一个点u,我们就可以考虑一下以u为lca的路径。枚举路径还是会爆掉,所以我们考虑到定量——只要是在u下方w[u]处出发的点,对答案都有贡献。也就是说我们走过每一个观察员,分别考虑这个点作为lca时到起点的路径和作为终点时从lca过来的路径。但是们得出这个点的位置只有其深度,所以我们可以开一个cnt1[maxn]处理前一条链,cnt2[maxn]处理第二条链

正如前文所解释的,我们处理第一条路径的时候是以当前节点为lca计算出在路径上满足第一个公式的点并看到在那个点出发的人,所以我们能处理的就只是深度。所以cnt1[ i ]可以表示在第i层出发可以被看到的人数。具体更新很简单,记录下当前的cnt值,计算一波后差分得出答案。但是有一个小细节——由于这条路径(u->v)在当前点就拐弯了,所以对于其他的非这个点子树的点都是没有任何贡献的,即使是满足了公式也都不应算作答案。所以我们要记录一下以这个节点为lca的起点,在差分的时候减去

第二条路径相对麻烦一些,因为我们的处理方式是作为终点。在当前思路下【是的因为第二个公式可以变形,以其他方式处理】,我们可以处理出等式两边的值,比如以v为第二条路径的终点时的或者前者【这里我们是处理的后者】,而后直接对照公式的另一端。所以我们这里就没有必要处理出以某点为终点的人数了,我们只能直接对照值。写个桶来统计就比较方便了。仍旧有几个小细节——比如公式两端值可能为负,用桶差分的话可能会非法访问,所以要同时加上n;和第一条路径的处理相同的,虽然我们是以当前节点为终点,但是以当前节点为lca的也是不需要了的,维护差分要减去。

思路就是这样了!!!【反正这么细节而又巧妙的思路我是想不到的QwQ】下面看代码吧,也有详解【看了我一个上午QwQ】

#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
int n, m;
int w[maxn];
int read()
{int x = 0, ch = getchar();while(!isdigit(ch)) ch = getchar();while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();return x;
}struct edge
{int to, nxt;edge() {}edge(int t, int x) {to = t, nxt = x;}
}e[maxn << 1];int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]); head[u] = k++;}int fa[maxn], dep[maxn], size[maxn], son[maxn], top[maxn], depst = 0;
struct hld//树剖求lca,整体我就打包了区分一下
{void dfs1(int u){size[u] = 1; register int v;for(int i = head[u]; ~i; i = e[i].nxt){v = e[i].to;if(v == fa[u]) continue;fa[v] = u; dep[v] = dep[u] + 1;depst = max(depst, dep[v]);dfs1(v);size[u] += size[v];if(size[v] > size[son[u]]) son[u] = v;}}void dfs2(int u, int tp){register int v;top[u] = tp;if(son[u]) dfs2(son[u], tp);for(int i = head[u]; ~i; i = e[i].nxt){v = e[i].to;if(v != fa[u] && v != son[u]) dfs2(v, v);}}int ask(int u, int v){while(top[u] != top[v]){if(dep[top[u]] > dep[top[v]]) swap(u, v);v = fa[top[v]];}return dep[u] > dep[v]? v : u;}
}HLD;struct node//存储每个人【每条路径】的信息
{int u, v, lca, dis;
}a[maxn];vector<int> v1[maxn], v2[maxn], v3[maxn];
int ans[maxn], cnt1[maxn], cnt2[maxn << 1], s[maxn];
void dfs_1(int u)//这两个dfs_是核心!!!!! dfs1处理u->lca路径
{register int v, x = w[u] + dep[u], tmp;//x为公式一中的涉及到的深度 if(x <= depst) tmp = cnt1[x];//注意x要判定合法性!!! depst在树剖初始化里更新的 for(int i = head[u]; ~i; i = e[i].nxt){v = e[i].to;if(v != fa[u]) dfs_1(v);}    cnt1[dep[u]] += s[u];//累计在这个点出发的点 if(x <= depst) ans[u] = cnt1[x] - tmp;//tmp记录更新前的值 ,这里差分 for(int i = 0; i < v1[u].size(); i++)//这条路径在这里就拐弯了不贡献了cnt1[dep[v1[u][i]]]--;
}void dfs_2(int u)//lca->v路径
{register int v, x = dep[u] - w[u] + n, tmp = cnt2[x];//x为目标值,tmp同样用于差分 for(int i = head[u]; ~i; i = e[i].nxt)//↑等式两边同时+n防止出现负数{v = e[i].to; if(v != fa[u]) dfs_2(v);}for(int i = 0; i < v2[u].size(); i++) cnt2[v2[u][i] + n]++;//以u为终点,访问到了,这些值对应都存在了,先++用于调用 ans[u] += cnt2[x] - tmp;//ans加上目标值,差分 for(int i = 0; i < v3[u].size(); i++) cnt2[v3[u][i] + n]--;//路径二同理去掉这些路径
}int main()
{memset(head, -1, sizeof head);n = read(), m = read();register int u, v;for(int i = 1; i < n; i++)u = read(), v = read(), add(u, v), add(v, u);for(int i = 1; i <= n; i++) w[i] = read();for(int i = 1; i <= m; i++) a[i].u = read(), a[i].v = read();HLD.dfs1(1); HLD.dfs2(1, 1);//加了HLD.的都是LCA的基本操作 for(int i = 1; i <= m; i++){u = a[i].u, v = a[i].v;s[u]++;a[i].lca = HLD.ask(u, v);a[i].dis = dep[u] + dep[v] - (dep[a[i].lca] << 1);v1[a[i].lca].push_back(u);//路径一的细节处理 v2[a[i].v].push_back(dep[v] - a[i].dis);//路径二的答案对应累计用 v3[a[i].lca].push_back(dep[v] - a[i].dis);//路径二的细节处理 }dfs_1(1);dfs_2(1);for(int i = 1; i <= m; i++)//特殊处理一下:如果这条路径本就是一条链且起点or终点有重复计算,那么lca就多算了一次的 if(dep[a[i].u] - dep[a[i].lca] == w[a[i].lca]) ans[a[i].lca]--;for(int i = 1; i <= n; i++)printf("%d ", ans[i]);return 0;
}

终!于!写!完!了!!!树上差分的毒瘤题!!!【对我这种树上差分的新人太不友好了】

迎评:)
——End——

NOIP2016·洛谷·天天爱跑步相关推荐

  1. noip2016 day1 t2 天天爱跑步

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

  2. 【NOIP2016】bzoj4719 天天爱跑步

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

  3. NOIP2016洛谷P1600:天天爱跑步

    文章目录 解析 sol1:树剖+map sol2:树剖+离线 sol3:dfs维护树状数组+差分 解析 个人认为本题比同年的逛公园可做许多 本题的一个关键是:把慢跑者(u,v)(u,v)(u,v)转化 ...

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

    如果没有时间的限制,这题就是对每个点iii,求经过iii的路径数,用树上差分解决即可: 枚举路径x→y{x\to y\{x→y{ a[x]+=1;a[y]+=1;a[x]+=1;a[y]+=1;a[x ...

  5. NOIP2016提高组 天天爱跑步

    (树上差分 + \(LCA\)) \(O(Mlog_2N)\) 调了两个小时,最后发现把\(lca\)里的\(y\)写成\(x\)了,当场去世. 首先下几个定义: \(dis[x]\) 为\(x\)到 ...

  6. 【洛谷】1600:天天爱跑步【LCA】【开桶】【容斥】【推式子】

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

  7. luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)

    阅读体验: https://zybuluo.com/Junlier/note/1303550 为什么这一篇的Markdown炸了? # 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席 ...

  8. NOIP2016天天爱跑步

    NOIP2016天天爱跑步 这题一看显然lca+树上差分,但是因为有w的限制不能直接加,所以考虑权值线段树合并, 每个选手的起点终点对于不同的节点的影响是不同的,这就非常麻烦了,但是可以发现无论如何他 ...

  9. NOIP2016天天爱跑步 题解报告【lca+树上统计(桶)】

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

最新文章

  1. android wear无法启用,android-wear – 无法创建Android虚拟设备,“没有为此目标安装系统映像”...
  2. “Linux频道”有链接打不开
  3. MySQL远程连接失败,MySQL远程连接出现Using password:YES错误的解决办法
  4. 百度移动终端研发工程师笔试题
  5. go语言触发异常的场景
  6. mjpg-streamer在Ubuntu下编译,运行
  7. Sql Server 2008R2版本中有关外键Foreign的使用
  8. 一款霸榜 GitHub 的开源 Linux 资源监视器!
  9. H5禁止手机自带键盘弹出
  10. C#并行编程(1):理解并行
  11. 8.11模拟:数据结构
  12. GridView”的控件 必须放在具有 runat=server 的窗体标记内 “错误提示”
  13. L2-029 特立独行的幸福 (25 分)-PAT 团体程序设计天梯赛 GPLT
  14. 彻底下载32位office2010
  15. 输入流控制:几种清除输入流中空格或回车的常用函数
  16. MySQL全量备份和增量备份脚本
  17. DMA内存申请--dma_alloc_coherent 及 寄存器与内存【转】
  18. 名师出高徒!请关注领英上这十位活跃的大神
  19. python 战棋游戏代码实现(1):生物行走和攻击选择
  20. C语言 | 计算某日是该年的第几天

热门文章

  1. Oracle Partner Levels and Diamond Partners List
  2. Target EDI 对接详解 – Partner Online EDI 注册
  3. 关于bitamap旋转图片全黑
  4. 微信扫码背后的图像超分辨率技术
  5. 【阿里云】学生成长计划领取资格考试答案分享
  6. Go 使用 buger/jsonparser 优化反序列化性能
  7. 实时翻译器-实时自动翻译器
  8. 2019年最新版百度网盘不限速下载工具,无需登录实现高速下载!
  9. 人到中年:最近看到的几篇好文
  10. 《宝岛双雄》曝正式海报 房祖名挑大梁的银幕转型之作