阅读体验:

https://zybuluo.com/Junlier/note/1303550

为什么这一篇的Markdown炸了?

# 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席树)标签:比较综合的题?

综合整理自:[洛谷P1600 天天爱跑步 题解](https://www.luogu.org/problemnew/solution/P1600)
提供两种方法,因为网上讲的很详细,~~我也就不需要再多说了~~
因为自己的代码不是完全按下面任意一种方法来的(有不同的地方)
~~所以代码也是复制的,不要介意。。。~~
可能以后再写一遍的时候放上自己的代码吧(或者那天心血来潮把自己**更复杂**的方法讲一遍)

## 桶&树上差分
### $25$分算法
直接暴力,跟着人走,当发现当前节点满足条件就统计
### $S$为根节点部分分
$S$为根节点,如果节点$i$可以观测到人,那么首先要满足`w[i]==deep[i]`,然后以i为根节点的子树包含多少个终点,$i$节点的答案就是几
### $T$为根节点
对于i节点,深度为`deep[i]+w[i]`的起点才会产生贡献。那就$dfs$树上统计呗:
> 1. 开一个桶`bac[x]`表示当前深度为$x$的起点有多少个
> 2. 对于节点$i$,访问时先记录当时的`bac[deep[i]+w[i]]`,再往下递归,递归完后检查`bac[deep[i]+w[i]]`,增加了$v$就说明它的子树有$v$个这样的起点,$i$的答案就是$v$
### 退化为链部分分

退化为链你能想到什么?
所有的路径要么左走要么右走
我们只考虑右走【左走类似】
右走时,对于节点$i$,只有节点`i-w[i]`为起点时才会产生贡献。
那就向右扫一遍:

> 1. 同样开一个桶`bac[x]`表示扫到现在以$x$为起点的还未走完的路径有多少个
> 2. 记录当前点$i$的答案`bac[i-w[i]]`
> 3. 对于在该点结束的路径的`bac[S]--`
### 满分算法

满分算法其实就是综上所述。。
先把树进行$lca$,路径分为向上和向下走
> 1. 对于向上走的路径,在i节点,当deep[i]+w[i]==deep[S]时才会产生贡献
借用以T为根节点的思想,开一个桶来差分统计就好了
> 2. 对于向下走的路径,在i节点,当deep[i]-w[i]==deep[T]-dis[S,T]-pret[T]时才会产生贡献
(dis表示路径长,pret表示若该路径起点为lca,则走到lca时是什么时刻,若该路径起点为自然起点,则xpret=0)
> 3. 进行同样的统计,到i节点时把向上路径的起点S_up和向下路径的终点T_up(起点在上面的终点)的对应的bac[ ]++
(例如T_up就是bac[deep[T]-dis[S,T]-pret[T]]++),在访问结束时将向下路径的起点S_down和向上路径的终点T_up对应的另一个端点的统计撤销(类似于链状部分分的算法,看不明白可以参照一下)
> 4. 若该点为lca且该点产生了贡献,贡献值应该-1,因为统计了两次

总的来说要注意的地方还是很多的,细节处理要特别注意

```
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define LL long long int
using namespace std;
const int maxn=700005,maxm=2000100,INF=2000000000,P=1000000007;
//快速读入
inline int read(){
int out=0,flag=1;char c=getchar();
while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
while(c>=48&&c<=57){out=out*10+c-48;c=getchar();}
return out*flag;
}
//边信息建立
int head[maxn],nedge=0;
struct EDGE{
int to,next;
}edge[maxm];
inline void build(int a,int b){
edge[nedge]=(EDGE){b,head[a]};
head[a]=nedge++;
edge[nedge]=(EDGE){a,head[b]};
head[b]=nedge++;
}
//lca询问信息建立
int Head[maxn],nlca=0;
struct LCA{
int to,flag,next;
}Lca[maxm];
inline void link(int a,int b){
Lca[nlca]=(LCA){b,0,Head[a]};
Head[a]=nlca++;
Lca[nlca]=(LCA){a,0,Head[b]};
Head[b]=nlca++;
}
int N,M,w[maxn],rt=0,Siz[maxn],disrt=INF;
//数据读入
void init(){
fill(head,head+maxn,-1);
fill(Head,Head+maxn,-1);
N=read();M=read();
int a,b;
for(int i=1;i<N;i++) build(read(),read());
for(int i=1;i<=N;i++) w[i]=read();
for(int i=1;i<=M;i++){a=read();b=read();link(a,b);}
}
//重心为根
void dfs1(int u,int f){
int to,Min=INF,Max=-INF;
Siz[u]=1;
for(int k=head[u];k!=-1;k=edge[k].next)
if((to=edge[k].to)!=f){
dfs1(to,u);
Siz[u]+=Siz[to];
if(Siz[to]<Min) Min=Siz[to];
if(Siz[to]>Max) Max=Siz[to];
}
if(Min==INF) return;
if(N-Siz[u]<Min&&f) Min=N-Siz[u];
if(N-Siz[u]>Max) Max=N-Siz[u];
if(Max-Min<disrt){disrt=Max-Min;rt=u;}
}
void focus(){
dfs1(1,0);
if(!rt) rt=1;
//cout<<rt<<endl;
}
vector<int> Su[maxn],Sd[maxn],Tu[maxn],Td[maxn];
int pre[maxn],dep[maxn],dis[maxn],S[maxn],T[maxn],pret[maxn],pathi=0,temp;
int lca0[maxn];
bool vis[maxn];
inline int find(int u){
return u==pre[u] ? u:pre[u]=find(pre[u]);
}
//tarjan_lca算法割路径
void dfs2(int u,int f){
int to;
pre[u]=u;
dep[u]=dep[f]+1;
vis[u]=true;
for(int k=head[u];k!=-1;k=edge[k].next){
if((to=edge[k].to)!=f){
dfs2(to,u);
pre[to]=u;
}
}
for(int k=Head[u];k!=-1;k=Lca[k].next){
if(!Lca[k].flag&&vis[to=Lca[k].to]){
Lca[k].flag=Lca[k^1].flag=true;
int flag=0,m=find(to);
if(!(k&1)) {flag=1;to^=u^=to^=u;}
pathi++;
if(to==m){
S[pathi]=to;T[pathi]=u;dis[pathi]=dep[u]-dep[to];
pret[pathi]=0;
Sd[to].push_back(pathi);Tu[u].push_back(pathi);
}else if(u==m){
S[pathi]=to;T[pathi]=u;dis[pathi]=dep[to]-dep[u];
Td[u].push_back(pathi);Su[to].push_back(pathi);
}else{
lca0[pathi]=m;
S[pathi]=to;T[pathi]=m;dis[pathi]=dep[to]-dep[m];
Su[to].push_back(pathi);Td[m].push_back(pathi);
S[++pathi]=m;T[pathi]=u;dis[pathi]=dep[u]-dep[m];
pret[pathi]=dep[to]-dep[m];
Sd[m].push_back(pathi);Tu[u].push_back(pathi);
}
if(flag) u=to;
}
}
}
void tarjan_lca(){
dfs2(rt,0);
/*for(int i=1;i<=pathi;i++){
printf("%d %d %d\n",S[i],T[i],dis[i]);
}*/
}
int cntS[maxm],cntT[maxm],ans[maxn];
//树上统计
void dfs(int u,int f){
int dS=dep[u]+w[u]+maxn,oriS=cntS[dS],dT=dep[u]-w[u]+maxn,oriT=cntT[dT],to;
for(unsigned i=0;i<Su[u].size();i++){
cntS[dep[S[Su[u][i]]]+maxn]++;
}
for(unsigned i=0;i<Tu[u].size();i++){
cntT[dep[T[Tu[u][i]]]-dis[Tu[u][i]]-pret[Tu[u][i]]+maxn]++;
}
for(int k=head[u];k!=-1;k=edge[k].next){
if((to=edge[k].to)!=f){
dfs(to,u);
}
}
ans[u]=cntS[dS]-oriS+cntT[dT]-oriT;
for(unsigned i=0;i<Td[u].size();i++){
cntS[dep[S[Td[u][i]]]+maxn]--;
//if(u==2) cout<<"lca:"<<lca0[Td[u][i]]<<endl;
if(lca0[Td[u][i]]==u&&dep[S[Td[u][i]]]+maxn==dS) ans[u]--;
}
for(unsigned i=0;i<Sd[u].size();i++){
cntT[dep[T[Sd[u][i]]]-dis[Sd[u][i]]-pret[Sd[u][i]]+maxn]--;
}
}
//打印答案
void print(){
printf("%d",ans[1]);
for(int i=2;i<=N;i++) printf(" %d",ans[i]);
printf("\n");
}
int main(){
init();
focus();
tarjan_lca();
dfs(rt,0);
print();
return 0;
}
```

## 树剖&主席树(哇,大佬Tyher)
下面那些式子就不推到了,看了上面的题解你就会发现其实桶的做法是比较难维护的因为树上差分这种东西毕竟思维难度还是比较高
因为桶做法的本质还是在$dfs$过程中到$i$节点,看$i$节点的子树中的桶有多少个起点满足要求,终点满足要求
然后在考虑$lca$取不取,去重复之类的巴拉巴拉
所以智商不够数据结构来凑
因为桶做法之所以麻烦就是因为在计算当前节点时会把其他子树中深度相同的点也算进来
为了方便,开$n+1$颗线段树,每颗线段树代表某一深度的点的集合,动态开点
然后考虑某一路径上的点能对哪一个深度的点产生影响
对于$s$到$lca$的路径,上面已经说的很清楚了,即$s$的深度的点
那么就在`root[de[s]]`这个线段树上,从$lca$到s的路径上所经过的点$+1$
这个路径$+1$的处理是可以树剖搞定的,$lca$也可以用树剖顺便求了
然后是$lca$到$t$的路径上,也是一样的道理,注意右移$Max$位
注意最后在`de[s]`线段树中单点减去$lca$的贡献,会重复一次的。
修改应当是区间修改,最后查询的时候一定要记得下放$lazy$
为了方便你可以开两颗主席树,分开考虑$s$到$lca$和$lca$到$t$
但是$val$,$ls$,$rs$几个数组可以共用
可以在树剖的时候就把改开的点全部开了
最后附上代码,看不懂可以私信
```
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define il inline
#define rg register
#define ll long long
#define ld long double
#define N 300000
#define inf 2147483647
using namespace std;
int n,m,u,v,cnt,num;
int hd[N],w[N],s[N],t[N];
int de[N],top[N],son[N],tot[N];
int fa[N],idx[N];
struct Edge{
int nt,to;
}edge[N<<1];
int rt1[N*2],rt2[N*2],ls[N*40],rs[N*40],val[N*40];
il void re(rg int &x);
il void link(rg int fm,rg int to);
void Dfs1(rg int i,rg int fm);
void Dfs2(rg int i,rg int head);
int lca(rg int x,rg int y);
il void work(rg int s,rg int t);
void add(rg int now,rg int le,rg int ri,rg int pos);
void update(rg int now,rg int le,rg int ri,rg int L,rg int R,rg int w);
int query(rg int now,rg int le,rg int ri,rg int pos);
int main(){
freopen("s.in","r",stdin);
re(n),re(m);
for(rg int i=1;i<n;++i){
re(u),re(v);
link(u,v),link(v,u);
}
for(rg int i=1;i<=n;++i)
re(w[i]);
for(rg int i=1;i<=m;++i)
re(s[i]),re(t[i]);
cnt=0,Dfs1(1,0),Dfs2(1,1);
for(rg int i=1;i<=m;++i)
work(s[i],t[i]);
for(rg int i=1;i<=n;++i){
int ans1=query(rt1[de[i]+w[i]],1,n,idx[i]);
int ans2=query(rt2[w[i]-de[i]+N],1,n,idx[i]);
printf("%d ",ans1+ans2);
}
return 0;
}
int query(rg int now,rg int le,rg int ri,rg int pos){
if(!now)return 0;
if(le==ri)return val[now];
if(val[now]){
if(ls[now])val[ls[now]]+=val[now];
if(rs[now])val[rs[now]]+=val[now];
val[now]=0;
}
rg int mid=((le+ri)>>1);
if(pos<=mid)return query(ls[now],le,mid,pos);
else return query(rs[now],mid+1,ri,pos);
}
il void work(rg int s,rg int t){
rg int Lca=lca(s,t),u=s;
while(top[u]!=top[Lca]){
update(rt1[de[s]],1,n,idx[top[u]],idx[u],1);
u=fa[top[u]];
}
update(rt1[de[s]],1,n,idx[Lca],idx[u],1);
u=t;
while(top[u]!=top[Lca]){
update(rt2[de[s]-2*de[Lca]+N],1,n,idx[top[u]],idx[u],1);
u=fa[top[u]];
}
update(rt2[de[s]-2*de[Lca]+N],1,n,idx[Lca],idx[u],1);
update(rt1[de[s]],1,n,idx[Lca],idx[Lca],-1);
}
void add(rg int now,rg int le,rg int ri,rg int pos){
if(le==ri)return;
rg int mid=((le+ri)>>1);
if(pos<=mid){
if(!ls[now])ls[now]=(++num);
add(ls[now],le,mid,pos);
}
else{
if(!rs[now])rs[now]=(++num);
add(rs[now],mid+1,ri,pos);
}
}
void update(rg int now,rg int le,rg int ri,rg int L,rg int R,rg int w){
if(!now)return;
if(L==le&&R==ri){val[now]+=w;return;}
rg int mid=((le+ri)>>1);
if(R<=mid)update(ls[now],le,mid,L,R,w);
else if(L>mid)update(rs[now],mid+1,ri,L,R,w);
else update(ls[now],le,mid,L,mid,w),update(rs[now],mid+1,ri,mid+1,R,w);
}
int lca(rg int x,rg int y){
while(top[x]!=top[y]){
if(de[top[x]]<de[top[y]])swap(x,y);
x=fa[top[x]];
}
if(de[x]>de[y])return y;
else return x;
}
void Dfs1(rg int i,rg int fm){
de[i]=de[fm]+1,fa[i]=fm;
tot[i]=1;
rg int maxn=0;
for(rg int k=hd[i];k;k=edge[k].nt){
rg int qw=edge[k].to;
if(qw==fm)continue;
Dfs1(qw,i),tot[i]+=tot[qw];
if(tot[qw]>maxn)maxn=tot[qw],son[i]=qw;
}
}
void Dfs2(rg int i,rg int head){
idx[i]=(++cnt),top[i]=head;
if(!rt1[de[i]+w[i]])rt1[de[i]+w[i]]=(++num);
add(rt1[de[i]+w[i]],1,n,idx[i]);
if(!rt2[w[i]-de[i]+N])rt2[w[i]-de[i]+N]=(++num);
add(rt2[w[i]-de[i]+N],1,n,idx[i]);
if(!son[i])return;
Dfs2(son[i],head);
for(rg int k=hd[i];k;k=edge[k].nt)
if(!idx[edge[k].to])
Dfs2(edge[k].to,edge[k].to);
}
il void re(rg int &x){
rg int res=0;rg int w=1;char c=getchar();
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')w=-1,c=getchar();
while(c>='0'&&c<='9')res=(res<<3)+(res<<1)+c-'0',c=getchar();
x=w*res;
}
il void link(rg int fm,rg int to){
edge[++cnt].nt=hd[fm];
edge[cnt].to=to;
hd[fm]=cnt;
}
```

luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)的更多相关文章

  1. LOJ #2359. 「NOIP2016」天天爱跑步(倍增+线段树合并)

    题意 LOJ #2359. 「NOIP2016」天天爱跑步 题解 考虑把一个玩家的路径 \((x, y)\) 拆成两条,一条是 \(x\) 到 \(lca\) ( \(x, y\) 最近公共祖先) 的 ...

  2. 天天爱跑步 [NOIP2016]

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

  3. 线段树&数链剖分

    傻逼线段树,傻逼数剖 线段树 定义: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现 ...

  4. [NOIP2016]天天爱跑步(树上差分+线段树合并)

    将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...

  5. 【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并

    题目描述 给出一棵n个点的树,以及m次操作,每次操作从起点向终点以每秒一条边的速度移动(初始时刻为0),最后对于每个点询问有多少次操作在经过该点的时刻为某值. 输入 第一行有两个整数N和M .其中N代 ...

  6. [LOJ3014][JOI 2019 Final]独特的城市——树的直径+长链剖分

    题目链接: [JOI 2019 Final]独特的城市 对于每个点,它的答案最大就是与它距离最远的点的距离. 而如果与它距离为$x$的点有大于等于两个,那么与它距离小于等于$x$的点都不会被计入答案. ...

  7. UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...

  8. BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP

    题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...

  9. CF487E Tourists 圆方树、树链剖分

    传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就 ...

随机推荐

  1. Docker设置容器开机自启动

    设置如下: docker update --restart=always 镜像ID 例如:docker update --restart=always e39a959d7bff. 参考:https:/ ...

  2. AI-sklearn 学习笔记(一)sklearn 一般概念

    scikit-learn Machine Learning in Python Simple and efficient tools for data mining and data analysis ...

  3. “没有找到mfc100u.dll”的解决方法

    现在需要安装 MindManager 2016 思维导图软件时,打开软件提示找不到 mfc100u.dll,无法执行程序.之前一直好好的,现在换电脑了安装提示这个问题,然后百度找的解决方案: 需要去微 ...

  4. 按字节读取txt文件缓存区大小设置多少比较好?

    读取 txt 文件常规写法有逐行读取和按照字节缓存读取,那么按照字节缓存读取时,设置缓存区多大比较好呢?百度了一下,没发现有说这个问题的,自测了一把,以事实说话. 常规读取方法如下: // 字节流读取 ...

  5. linux--mongodb安装与配置

    linux下的mongodb的安装: 在mongodb的官网上下载:mongodb-linux-x86_64-rhel62-3.2.3.gz1.解压: tar -xvf mongodb-linux-x ...

  6. 剖析 Vue.js 内部运行机制 (1)

    1. new Vue() 之后. Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周 期.事件. props. methods. data. computed ...

  7. Nginx+lua_Nginx+GraphicsMagick来实现实时缩略图

    1.安装GraphicsMagick cd /usr/local/src wget http://sourceforge.net/projects/graphicsmagick/files/graph ...

  8. Java EE的优越性主要表现在哪些方面

    J2 EE的优越性主要表现在哪些方面 J2EE基于JAVA 技术,与平台无关. J2EE拥有开放标准,许多大型公司实现了对该规范支持的应用服务器.如BEA ,IBM,ORACLE等. J2EE提供相当 ...

  9. MyBatis 中的#和$的区别

    #相当于对数据 加上 双引号,$相当于直接显示数据 #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sql时的 ...

  10. Java学习、面试、求职、干货资源精品合集

    本系列文章整合了本号发表和转载过的,有关Java学习.进阶.面试.做项目.求职经验等方面的文章,希望对想要找工作,以及正在找工作的你,能够有所帮助. 原创Java学习专题文章: 如何才能够系统地学习J ...