天天爱跑步 [NOIP2016]
Description
Input
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。
Output
输出1行n个整数,第j个整数表示结点j的观察员可以观察到多少人。
Sample Input
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
Sample Output
2 0 0 1 1 1
Hint
提示:
对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。
对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。
对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。
对于4号点,玩家1被观察到,共1人被观察到。
对于5号点,玩家2被观察到,共1人被观察到。
对于6号点,玩家3被观察到,共1人被观察到。

如果你的程序需要用到较大的栈空间(这通常意味着需要较深层数的递归),请务必仔细阅读选手目录下的文档running/stackpdf,以了解在最终评测时栈空间的限制与在当前工作环境下调整栈空间限制的方法。
Solution
以下部分分算法引自 传送门
测试点1——5:
数组a[i][j]表示点i在j时刻经过的人数,然后一个人一个人的dfs,记录时刻time
如果到了终点,递归回退时a[now][time]++
令watch[i]=j表示i号节点观察员在j时刻出现
输出a[i][watch[i]]
void run(int now,int end,int time,int fa)
{
if(now==end)
{
ok=true;
a[now][time]++;
return;
}
if(ok) return;
for(int i=front[now];i;i=nextt[i])
{
if(to[i]==fa) continue;
run(to[i],end,time+,now);
}
if(ok) a[now][time]++;
}
25分
测试点6——8:
树退化为一条链,而且点的编号就是深度
所以对于链上一个观察员,他只能观察到从两个位置出发的人i+watch[i],i-watch[i]
如果i能观察到起点深度比他小的人,那么这个人的终点要>=i
如果i能观察到起点深度比他大的人,那么这个人的终点要<=i
所以,
用链表存储从每个节点起跑的人,
枚举每个观察员,然后判断两个位置的人是否满足要求
void solve2()
{
for(int i=;i<=n;i++)
{
if(i-watch[i]>=)
{
for(int j=front[i-watch[i]];j;j=nextt[j])
{
if(i<=e[to[j]].t) ans[i]++;
}
}
if(i+watch[i]<=n)
{
for(int j=front[i+watch[i]];j;j=nextt[j])
{
if(i>=e[to[j]].t) ans[i]++;
}
}
}
}
40分
测试点9——12:
所有人的起点都是1,假设1号的深度为0,
那么只有观察员节点的深度等于出现时间,他才能观察到
所以先判断深度是否等于出现时间,不等,直接输出0,下一个
若相等,他一定能观察到经过他的所有人
也就是说,以观察员i为根的子树中有跑步者的终点,观察员i就能观察多少人
所以,终点+1,记录每个节点的深度,我用的bfs,单后dfs统计子树1的个数,
kids[]表示答案
void bfs1()
{
queue<node2>q;
cur.d= ;cur.who=; fa[]=;
q.push(cur);
while(!q.empty())
{
cur=q.front(); q.pop();
for(int i=front[cur.who];i;i=nextt[i])
{
if(to[i]==fa[cur.who]) continue;
fa[to[i]]=cur.who;
nxt.d=cur.d+; nxt.who=to[i]; deep[nxt.who]=deep[cur.who]+;
q.push(nxt);
}
}
}
void dfs1(int now)
{
kids[now]=sum[now];
for(int i=front[now];i;i=nextt[i])
{
if(to[i]==fa[now]) continue;
dfs1(to[i]);
kids[now]+=kids[to[i]];
}
}
60分
测试点13——16:
所有人的终点都是1
也就是说,第i个观察员只能观察到第deep[i]+watch[i]层的跑步者
所以,若跑步者能被观察员i观察到,要满足以下两个条件:
1、在起点观察员i的子树内
2、起点的层数=deep[i]+watch[i]
先对原树dfs一遍,记录观察员i的子树dfs序范围in[i]——out[i]
对每一层维护一颗线段树,动态开节点
查询时,找到deep[i]+watch[i]这一层的线段树,查in[i]——out[i]有多少个起点
void add(int &now,int pos,int l,int r,int cnt)
{
if(!now) now=++tot2;
sum[now]+=cnt;
if(l==r) return;
int mid=l+r>>;
if(pos<=mid) add(lc[now],pos,l,mid,cnt);
else add(rc[now],pos,mid+,r,cnt);
}
void dfs(int now,int pre)
{
in[now]=++tot;
add(root[deep[now]],tot,,n,siz[now]);
for(int i=front[now];i;i=nxt[i])
{
if(to[i]==pre) continue;
deep[to[i]]=deep[now]+;
maxn=max(maxn,deep[to[i]]);
dfs(to[i],now);
}
out[now]=tot;
}
void query(int now,int opl,int opr,int l,int r)
{
if(!now) return;
if(l>=opl&&r<=opr) { ans+=sum[now]; return; }
int mid=l+r>>;
if(opl<=mid) query(lc[now],opl,opr,l,mid);
if(opr>mid) query(rc[now],opl,opr,mid+,r);
}
-----------------------------------------------引用内容结束-----------------------------------------------------
正解
对于一条路径u->v,我们可以剖成往上和往下的两部分
对于向上的u->lca,如果中间有点i满足dep[i]+w[i]==dep[u],则这条路径会对i的答案产生贡献
对于向下的lca->v,如果中间有点i满足dep[i]-w[i]==dep[v]-dis(u,v),则这条路径会对i的答案产生贡献
那么我们考虑在dfs时维护每一层的起点数,对于向上的时候,对答案产生贡献开始于u点,终止于lca点,对于向下的时候,对答案产生贡献开始于v点,终止于lca点。
那么我们在一开始用三个数组v1(i)记录下路径lca为i的起点深度,v2(i)记录下路径终点为i的终点深度-dis(路径起点,路径终点),v3(i)记录下路径lca为i的终点深度-dis(路径起点,路径终点)
我们就可以计算向上跑时,在dfs每一个点的时候 向该层加入该点的路径数,统计答案,删除以该点为lca的路径数
同理向下跑 向该层加入终点为该点的路径数,统计答案,删除以该点为lca的路径数
Code
#include<bits/stdc++.h>
#define RG register int
#define rep(i,a,b) for(RG i=a;i<=b;++i)
#define add(u,v) e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt,swap(u,v),e[++cnt].v=v,e[cnt].next=head[u],head[u]=cnt
#define maxn 300100
#define WY 300000
using namespace std;
int n,m,cnt;
int fa[maxn],son[maxn],sz[maxn],top[maxn],dep[maxn],head[maxn],num[maxn],tot[maxn<<],ans[maxn],w[maxn];
vector<int> v1[maxn],v2[maxn],v3[maxn];
struct E{
int v,next;
}e[maxn<<];
inline int read()
{
int x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
void dfs1(int u,int pa)
{
sz[u]=,fa[u]=pa,dep[u]=dep[pa]+;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v; if(v==pa)continue;
dfs1(v,u); sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;
if(!son[u]) return;dfs2(son[u],tp);
for(int i=head[u];i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=son[u]) dfs2(e[i].v,e[i].v);
}
int find(int x,int y)
{
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void go_up(int u)
{
int len=dep[u]+w[u]; //计算 离 对目前位置可以产生贡献的起点 的距离
ans[u]-=tot[len]; //减去其他子树中的数量
for(int i=head[u];i;i=e[i].next)
if(e[i].v!=fa[u]) go_up(e[i].v);
tot[dep[u]]+=num[u]; //加入目前点的起点数
ans[u]+=tot[len]; //计算贡献
for(int i=,lim=v1[u].size();i<lim;i++) --tot[v1[u][i]]; //减去桶中lca为该点的统计
}
void go_down(int u)
{
int len=dep[u]-w[u]+WY; //加上定值防止len<0
ans[u]-=tot[len]; //减去其他子树中的数量
for(int i=head[u];i;i=e[i].next)
if(e[i].v!=fa[u]) go_down(e[i].v);
for(int i=,lim=v2[u].size();i<lim;i++) ++tot[v2[u][i]]; //加入范围内(v==now)的点
ans[u]+=tot[len];
for(int i=,lim=v3[u].size();i<lim;i++) --tot[v3[u][i]]; //减去范围外(lca==now)的点
}
int main()
{
n=read(),m=read();
for(RG i=,u,v;i<n;i++) u=read(),v=read(),add(u,v);
dfs1(,);dfs2(,);
rep(i,,n) w[i]=read();
rep(i,,m)
{
int u=read(),v=read(),lca=find(u,v);
++num[u];int len=dep[u]+dep[v]-(dep[lca]<<);
v1[lca].push_back(dep[u]),v2[v].push_back(dep[v]-len+WY),v3[lca].push_back(dep[v]-len+WY);
if(w[lca]+dep[lca]==dep[u]) --ans[lca];
}
go_up();memset(tot,,sizeof(tot));
go_down();
rep(i,,n) printf("%d ",ans[i]);
return ;
}
天天爱跑步 [NOIP2016]的更多相关文章
- luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)
阅读体验: https://zybuluo.com/Junlier/note/1303550 为什么这一篇的Markdown炸了? # 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席 ...
- LOJ #2359. 「NOIP2016」天天爱跑步(倍增+线段树合并)
题意 LOJ #2359. 「NOIP2016」天天爱跑步 题解 考虑把一个玩家的路径 \((x, y)\) 拆成两条,一条是 \(x\) 到 \(lca\) ( \(x, y\) 最近公共祖先) 的 ...
- [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]
[NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...
- [NOIp2016]天天爱跑步 线段树合并
[NOIp2016]天天爱跑步 LG传送门 作为一道被毒瘤出题人们玩坏了的NOIp经典题,我们先不看毒瘤的"动态爱跑步"和"天天爱仙人掌",回归一下本来的味道. ...
- NOIP2016天天爱跑步 题解报告【lca+树上统计(桶)】
题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 nn个 ...
- [Noip2016]天天爱跑步 LCA+DFS
[Noip2016]天天爱跑步 Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时上线,完成打卡任 ...
- 【NOIP2016】DAY1 T2 天天爱跑步
[NOIP2016]DAY1 T2 天天爱跑步 Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时 ...
- 洛谷 题解 P1600 【天天爱跑步】 (NOIP2016)
必须得说,这是一道难题(尤其对于我这样普及组205分的蒟蒻) 提交结果(NOIP2016 天天爱跑步): OJ名 编号 题目 状态 分数 总时间 内存 代码 / 答案文件 提交者 提交时间 Libre ...
- 【LG1600】[NOIP2016]天天爱跑步
[LG1600][NOIP2016]天天爱跑步 题面 洛谷 题解 考虑一条路径\(S\rightarrow T\)是如何给一个观测点\(x\)造成贡献的, 一种是从\(x\)的子树内出来,另外一种是从 ...
随机推荐
- 20165328 预备作业3 Linux安装及命令
Linux安装及学习 Linux安装遇到的问题: 问题:在我开始安装虚拟机的时候,在安装过程中总会出现初始界面,且无法跳过,陷入死循环. 解决方法:我在网上百度搜索该问题之后得到了答案,第一个界面是要 ...
- python:字符串转换成字节的三种方式及字符转码问题
str='zifuchuang' 第一种 b'zifuchuang'第二种bytes('zifuchuang',encoding='utf-8')第三种('zifuchuang').encode('u ...
- Task任务的屏障机制
Barrier 是 .Net 提供的一直并发的机制,它允许多个任务同步他们不同阶段的并发工作. 这里的关键点是[多个任务]和[不同阶段]. 假设有4个相同的任务(Task),每个任务都有4个阶段(Ph ...
- Redis cluster集群模式的原理
redis cluster redis cluster是Redis的分布式解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求 自动将数据进行分片,每个master上放一部分数据 提供内 ...
- [转] js前端解决跨域问题的8种方案(最新最全)
1.同源策略如下: URL 说明 是否允许通信 http://www.a.com/a.jshttp://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a.j ...
- 自定义rem
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【bzoj3589】动态树 树链剖分+树链的并
题解: 树链剖分是显然的 问题在于求树链的并 比较简单的方法是 用线段树打标记覆盖,查询标记区间大小 Qlog^2n 代码: #include <bits/stdc++.h> using ...
- eclipse启动web应用
在建好web项目的基础上: (1)配置tomcat服务器 点击window---->Preference----->Server---->Runtime Environment--- ...
- Codeforces 1136E Nastya Hasn't Written a Legend 线段树
vp的时候没码出来.. 我们用set去维护, 每一块区域, 每块区域内的元素与下一个元素的差值刚好为ki,每次加值的时候我们暴力合并, 可以发现我们最多合并O(n)次. 然后写个线段树就没了. #in ...
- 在cron运行hive时,无法打出mapreduce日志
本身我是这么运行的: 15 1 * * * /data/xx/shells/run.sh >> /data/xx/log/joblog/job.log 发现job.log中,没有打出hiv ...