luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)
阅读体验:
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)(主席树+树链剖分)的更多相关文章
- LOJ #2359. 「NOIP2016」天天爱跑步(倍增+线段树合并)
题意 LOJ #2359. 「NOIP2016」天天爱跑步 题解 考虑把一个玩家的路径 \((x, y)\) 拆成两条,一条是 \(x\) 到 \(lca\) ( \(x, y\) 最近公共祖先) 的 ...
- 天天爱跑步 [NOIP2016]
Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务.这个游戏的地图可 ...
- 线段树&数链剖分
傻逼线段树,傻逼数剖 线段树 定义: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现 ...
- [NOIP2016]天天爱跑步(树上差分+线段树合并)
将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...
- 【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并
题目描述 给出一棵n个点的树,以及m次操作,每次操作从起点向终点以每秒一条边的速度移动(初始时刻为0),最后对于每个点询问有多少次操作在经过该点的时刻为某值. 输入 第一行有两个整数N和M .其中N代 ...
- [LOJ3014][JOI 2019 Final]独特的城市——树的直径+长链剖分
题目链接: [JOI 2019 Final]独特的城市 对于每个点,它的答案最大就是与它距离最远的点的距离. 而如果与它距离为$x$的点有大于等于两个,那么与它距离小于等于$x$的点都不会被计入答案. ...
- UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...
- BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP
题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...
- CF487E Tourists 圆方树、树链剖分
传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就 ...
随机推荐
- JS window对象 返回前一个浏览的页面 back()方法
JS window对象 返回前一个浏览的页面 back()方法,加载 history 列表中的前一个 URL. 语法: window.history.back(); 返回前一个浏览的页面 back ...
- zabbix 4.2 发送警告邮件Python脚本
#!/usr/bin/env python#-*- coding: UTF-8 -*-import os,sysimport getoptimport smtplibfrom email.MIMETe ...
- mysql查询每个部门/班级前几名
Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id . +----+-------+--------+--------------+ | I ...
- shell getopts用法详解
本文链接:https://blog.csdn.net/u012703795/article/details/46124519 获取UNIX类型的选项: unix有一个优点就是标准UNIX命令在执行时都 ...
- shell 操作mysql
1.连接 #!/bin/bash user="root" password="XXXXXX" sql = `mysql -u>/dev/null use ...
- Java常用类库API之MD5简单使用
常用类库--MD5简单使用 MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash v ...
- Redirecting to /bin/systemctl restart mysql. service Failed to restart mysql.service: Unit not found.
使用如下命令操作mysql即可: systemctl restart mysqld.service systemctl start mysqld.service systemctl stop mysq ...
- The list of list is modified unexpected, python
Be careful! The list of list is modified unexpected, python # code patch A: list = [1,2,3,4,5,6,7] p ...
- 饿了么CTO张雪峰:允许90后的技术人员“浮躁“一点
编者按:今年4月,饿了么正式加入了阿里新零售战队,进一步加速其在本地生活市场的扩张速度.在创业9年的时间中,饿了么在外卖领域经历了真正的“从0到1”,尤其是在外卖平台的技术升级方面,越过了一个又一个的 ...
- php count_chars()函数 语法
php count_chars()函数 语法 作用:返回一个字符串,包含所有在字符串中使用过的不同字符.直线电机选型 语法:count_chars(string,mode) 参数: 参数 描述 str ...