题目描述

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

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

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

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

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

解析

可能是目前为止我做过的最难的题了。。。(话说这题为什么不是黑的)

再吐槽一句,考场上谁想得出这种神仙算法啊?!勉强拿个80暴力分都不容易啊!


在讲这题前,首先要引入一个船新的概念:全局桶

还有一些老东西:树上差分,LCA。

【全局桶】

全局桶,顾名思义,也是种桶,但是其维护的不是对象对应的下标,而是类似用桶来装权值(怎么说着那么奇怪)。精确的说,它维护的是对象产生的值出现的次数,而不是对象出现的次数。有点说不清楚,解释一下,比如这题,我们的全局桶将要维护的是每一个玩家的起点、终点对其跑步的路径所造成的影响,这个影响也就是起点、终点、路径上的观察员共同产生的一个值,下面会详细讲。我们会看到,它不维护树上某一个点的权值,而是作为总体的计数数组而存在。

【LCA】

即最近公共祖先,不必多言,看名字就知道啥意思。

【树上差分】

为了快速计算子树和,或维护子树的一些满足区间可减性(即满足该信息的前缀和可以相减,其可作为前缀和的逆运算)的信息时,树上差分可以很好胜任。类比序列上的差分,假设我们有一棵树\(T\),要在一条\(s\sim t\)的路径上对每个点的权值加\(k\),我们可以对差分数组执行以下操作:在\(s\)加\(k\),在\(t\)加\(k\),在\(lca(s,t)\)减\(k\),在\(father(lca(s,t))\)减\(k\),那么以\(lca(s,t)\)为根的子树前缀和(从叶子节点向根节点做前缀和,根节点算在内)就是原来的子树和。(树剖或LCT可取而代之)

解释完概念之后,我们来着手这道题的解决。

分析题目,容易得出每个玩家其实就是对\(s\sim t\)的路径上的点依次增加了\(1\sim len(s,t)\)的权值。故有暴力算法,对每个运动员,我们处理出他跑的路径对树的贡献,最后暴力dfs整棵树统计答案,复杂度\(O(nm)\)。优化的好的话就能得到80分了。。。

似乎和树上差分没什么关系?

我们不妨转化一下思维,逆向看问题。

对于树上每个节点,它都有可能作为某一对起点和终点的LCA,假设某个在\(s\sim t\)上的观察员为\(x\)。而\(x\)对最终答案产生贡献仅当\(deep[x]+w[x]=deep[s]\)或\(deep[x]+deep[s]-deep[lca(s,t)]*2=w[x]\)时。这是比较显而易见的表达方式,然后我们将其移项,使得其呈现规律性:\(deep[s]=deep[x]+w[x]\),\(deep[s]-deep[lca(s,t)]*2=w[x]-deep[x]\)。好的!现在关于\(x\)的式子都在等号左边了。这就方便了我们来维护这些等式,但这也是难点所在。

根据以上分析,我们就得分\(s\sim lca(s,t)\),\(lca(s,t) \sim t\)两条路径讨论了。

我们先考察稍微简单一点的\(s\sim lca(s,t)\)这条路径。

不难想到,对于一个玩家,它的起点\(s\)会给\(s\sim lca(s,t)\)上的所有点\(x\)造成\(deep[s]\)的贡献,只有当\(x\)的贡献\(deep[x]+w[x]\)与其相等时,\(x\)才能观察到该玩家,该点答案\(+1\)。换句话说,就是对该路径上所有点加上\(deep[x]\)。你想到了什么?没错,树上差分,这是很自然的一个推导过程。假设这个差分数组为数组\(c\),此时我们对\(c[s]\)加\(deep[s]\),对\(c[lca(s,t)]\)减\(deep[x]\)。

对于\(s\sim lca(s,t)\)这条路径呢,我们就给\(c[lca(s,t)]\)减去\(deep[s]-deep[lca(s,t)]*2\),给\(c[t]\)加上\(deep[s]-deep[lca(s,t)]*2\)。等等,LCA怎么算重了???没事,你只减一条路上LCA就行,你会发现在LCA这里,两个等式是等价的。复杂度因人而异,但肯定不会超过\(O(nm)\)啦。

这样做,你需要线段树合并,或者别的数据结构来维护这个差分数组。你愉快的发现,你T了。

我们还没拿出来全局桶呢,不得不说,这个办法太秀了,也比较抽象。我们再逆向一下思维,考虑所有点作为LCA时的情况

说白了其实就是上面那种方法逆过来搞,可以实现一种先处理所有玩家最后统计答案的算法,复杂度可以达到\(O((n+m)log(n+m))\)。

这个全局桶,它的好处就是去掉了许多无用的条件,只保留最少的我们所需要的条件。

建立全局桶\(bucket\),对每种类型的贡献(比如\(deep[x]+w[x]\))进行计数。

对树上每一个节点开4个多重集(比如vector),将所有经过该节点的玩家的起点放进一个集合,终点放进一个集合,将该节点作为一条路径的终点放进一个集合,将该节点作为一条路径的起点放进一个集合。首先起点和终点直接的路径时一定的,且一定会经过它们的LCA。那么对于某个节点\(x\),第一个集合也就对应着所有经过\(x\)的路径,或者说以\(x\)为LCA的起点的贡献,第二个集合对应着所有以\(x\)为LCA的终点的贡献。

最后,dfs整颗树,计算树上前缀和。具体来说,对于\(s\sim lca(s,t)\),就是递归进入在这条路径上的点\(x\)时,对于第一个集合中的点集\(i_1\),给\(bucket[deep[i_1]]-1\),对于第二个集合,也给\(bucket[deep[i_2]]-1\),对于第三个集合,给\(bucket[deep[i_3]]+1\),对第四个集合,给\(bucket[deep[i_4]]+1\)。等我们又回溯到\(x\)时,它的子树已经对桶完成更新,意即所有能被\(x\)观察到的玩家都被算进了\(bucket[deep[x]+w[x]]\),因此此时\(bucket[deep[x]+w[x]]\)前后的差值就是\(x\)点对答案的贡献。

发现又加重了,我们最后减掉就行。

注意,对于\(lca(s,t)\sim t\)这条路径,统计的时候桶下标会溢出,我们要离散化或者平移坐标处理。

回顾树上差分方法,你会发现二者其实在做同一件事情,而后者真是惊艳到我了。

这里给出一种全局桶的做法,简化了第四个集合(其实是“借鉴”题解的):

参考代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define N 600010
#define M 300010
using namespace std;
inline int read()
{
int f=1,x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int f[31][N],n,m,a[N+M],c[N],dep[N],t,sum[N],ans[N];
vector<int> v1[M],v2[M],v3[M];
struct rec{
int next,ver;
}g[N];
int head[N],tot;
struct node{
int s,t,dis,lca;
}s[M];
inline void add(int x,int y)
{
g[++tot].ver=y;
g[tot].next=head[x],head[x]=tot;
}
inline void init()
{
queue<int> q;
q.push(1);dep[1]=1;
while(q.size()){
int x=q.front();q.pop();
for(int i=head[x];i;i=g[i].next){
int y=g[i].ver;
if(dep[y]) continue;
dep[y]=dep[x]+1;
f[0][y]=x;
for(int j=1;j<=t;++j)
f[j][y]=f[j-1][f[j-1][y]];
q.push(y);
}
}
}
inline int lca(int x,int y)
{
if(dep[y]>dep[x]) swap(x,y);
for(int j=t;j>=0;--j)
if(dep[y]<=dep[f[j][x]]) x=f[j][x];
if(x==y) return x;
for(int j=t;j>=0;--j)
if(f[j][x]!=f[j][y]) x=f[j][x],y=f[j][y];
return f[0][x];
}
inline void dfs1(int x,int fa)
{
int cnt=c[dep[x]+a[x]+M];
for(int i=head[x];i;i=g[i].next){
int y=g[i].ver;
if(y==fa) continue;
dfs1(y,x);
}
c[dep[x]+M]+=sum[x];
ans[x]+=c[dep[x]+a[x]+M]-cnt;
for(int i=0;i<v1[x].size();++i)
c[v1[x][i]+M]--;
}
inline void dfs2(int x,int fa)
{
int cnt=c[a[x]-dep[x]+M];
for(int i=head[x];i;i=g[i].next){
int y=g[i].ver;
if(y==fa) continue;
dfs2(y,x);
}
for(int i=0;i<v3[x].size();++i)
c[v3[x][i]+M]++;
ans[x]+=c[a[x]-dep[x]+M]-cnt;
for(int i=0;i<v2[x].size();++i)
c[v2[x][i]+M]--;
}
int main()
{
n=read(),m=read();
t=log2(n)+1;
for(int i=1;i<n;++i){
int u=read(),v=read();
add(u,v),add(v,u);
}
init();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=m;++i){
int f=read(),t=read();
s[i].s=f,s[i].t=t;
s[i].lca=lca(f,t);
s[i].dis=dep[f]+dep[t]-dep[s[i].lca]*2;
sum[f]++;
v1[s[i].lca].push_back(dep[f]);
v2[s[i].lca].push_back(dep[f]-dep[s[i].lca]*2);
v3[t].push_back(dep[f]-dep[s[i].lca]*2);
}
dfs1(1,-1);
dfs2(1,-1);
for(int i=1;i<=m;++i)
if(dep[s[i].s]==dep[s[i].lca]+a[s[i].lca]) ans[s[i].lca]--;
for(int i=1;i<=n;++i)
printf("%d ",ans[i]);
return 0;
}

P1600 天天爱跑步[桶+LCA+树上差分]的更多相关文章

  1. 洛谷 P1600 天天爱跑步(LCA+乱搞)

    传送门 我们把每一条路径拆成$u->lca$和$lca->v$的路径 先考虑$u->lca$,如果这条路径会对路径上的某一个点产生贡献,那么满足$dep[u]-dep[x]=w[x] ...

  2. [NOIP 2016D2T2/Luogu P1600] 天天爱跑步 (LCA+差分)

    待填坑 Code //Luogu P1600 天天爱跑步 //Apr,4th,2018 //树上差分+LCA #include<iostream> #include<cstdio&g ...

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

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

  4. Luogu:P1600 天天爱跑步

    来一发清新的80行 树剖 $LCA$ + 树上差分 题解. -----from Judge 本题题意大概是给出一棵 n 个节点的树以及 m 条有向路径, 并且每个点 i 都有一个权值 $w[i]$,如 ...

  5. [BZOJ3307]:雨天的尾巴(LCA+树上差分+权值线段树)

    题目传送门 题目描述: N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入格式: 第一 ...

  6. 洛谷P1600 天天爱跑步——树上差分

    题目:https://www.luogu.org/problemnew/show/P1600 看博客:https://blog.csdn.net/clove_unique/article/detail ...

  7. 洛谷$P1600$ 天天爱跑步 树上差分

    正解:树上差分 解题报告: 传送门$QwQ$! 这题还挺妙的,,,我想了半天才会$kk$ 首先对一条链$S-T$,考虑先将它拆成$S-LCA$和$LCA-T$,分别做.因为总体上来说差不多接下来我就只 ...

  8. [luogu]P1600 天天爱跑步[LCA]

    [luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...

  9. 洛谷P1600 天天爱跑步(线段树合并)

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

随机推荐

  1. 西门子PLC1200内使用SCL实现简化版PID算法

    西门子自带的PID效果很好,但是会比较吃性能,使用次数有限,很多地方需要PID但不需要这么精准的PID,所以网上找个简单的算法自己调用. 新建数据类型 前三个就是PID三个参数 新建FC块: #PID ...

  2. E: Unable to correct problems, you have held broken packages-之apt-get 下载报依赖问题

    今天在新来了一台ubutnu 18.04 在安装zabbix客户端是报依赖问题 root@VM_0_10:~# apt-get install zabbix-agent Reading package ...

  3. 【神经网络与深度学习】【计算机视觉】YOLO2

    YOLO2 转自:https://zhuanlan.zhihu.com/p/25167153?refer=xiaoleimlnote 本文是对 YOLO9000: Better, Faster, St ...

  4. python基础(二)-- 列表、字典、集合、字符串操作

    4.列表: 基本操作: 索引 切片 追加 删除 长度 切片 循环 包含 import copy i=0 #persons=['dailaoban','xiekeng',['age',100,50],' ...

  5. Javaweb的概念与C/S、B/S体系结构

    大家好,乐字节的小乐又来了,今天的文章是接上次<客户端请求服务器端通信, Web 编程发展基础|乐字节>,这次是讲述Javaweb的介绍和C/S.B/S体系结构. 一.Javaweb的概念 ...

  6. [转帖]VMware vSphere 6 序列号大全

    VMware vSphere 6 序列号大全 https://blog.csdn.net/sj349781478/article/details/82378244   经过测试ESXI6.5也可以使用 ...

  7. MySQL中主键id不连贯重置处理办法

    MySQL中有时候会出现主键字段不连续,或者顺序乱了,想重置从1开始自增,下面处理方法 先删除原有主键,再新增新主键字段就好了 #删除原有自增主键 ALTER TABLE appraiser_info ...

  8. kotlin --- 时间戳与字符串互相转换

    直接贴代码,清晰易懂.喜欢点个赞 class Timestamp { /** * Timestamp to String * @param Timestamp * @return String */ ...

  9. CF1097G Vladislav and a Great Legend 组合、树形背包

    传送门 看到\(k\)次幂求和先用斯特林数拆幂:\(x^k = \sum\limits_{i=1}^k \binom{x}{i}\left\{ \begin{array}{cccc} k \\ i \ ...

  10. Centos7 在线安装开发环境 jdk1.8+mysql+tomcat

    写在最前 刚刚开始接触Linux,并折腾着在服务器上部署自己的项目,当然作为一个后端开发人员,必不可少的东西肯定是 JDK Mysql Tomcat容器 每天记录一天,每天进步一点点~~ 1.更新系统 ...