CJOJ——P2253 - 【NOIP2016】天天爱跑步
题意
??给定一棵有n个节点的树,每个节点有一个属性
Wi
??树上有m个人,每个人要从Si 到达Ti,每秒可以移动一条边。求在第Wi秒到达第i个节点的人的数目
解法
LCA+树上差分统计:
这道题的暴力分很多,有80分。对于n,m≤1000 的点,可以直接枚举每一个人的路径,暴力统计答案(25分)
??对于Si=1的点,那就说明S为根节点,那么只有depi=Wi 的节点才可以被统计到(20分)
??对于Ti=1的点,对于一个深度为depi的节点,一个人能在Wi的时刻到达该点,说明这个人的起点的深度是depi+Wi,所以我们可以开一个桶:bacx表示深度为x的节点有多少个,那么到达点k 的时候,我们记录此时的bacdepk+Wk,当递归完成后bacdepk+Wk增加的部分就是k的答案,(20分)
对于一条链的情况,一个人在链上要么往左走,要么往右走。我们只考虑往右走的情况:对于点i ,只有点i?wi为起点时才会产生贡献,那就向右扫一遍:
??1、开一个桶bacx表示到现在以x为起点的还没有到终点的人有多少个
2、记录当前点i 的答案baci?wi
??3、对于在该点结束的人的bacS–
??(15分)
??对于普遍情况,可以将一个人走的路径分为向上走和向下走两个部分(即:Si??>LCASi,Ti??>Ti):
??对于向上走的路径,在i节点,当depi+wi=depS 时才会产生贡献,借用以T为根节点的思想,开一个桶来差分统计就好了
??对于向下走的路径,在i节点,当depi?wi=depT?disS,T?preT 时才会产生贡献,pre表示若该路径起点为LCA,则走到LCA时是什么时刻,若该路径起点为自然起点,则pre=0
??然后进行同样的统计,到i节点时把向上路径的起点Sup 和向下路径的终点Tup【起点在上面的终点】的对应的bac++,例如Tup就是bacdepT?disS,T?preT++,在访问结束时将向下路径的起点Sdown和向上路径的终点Tup对应的另一个端点的统计撤销,这个有点类似于链状部分分的算法
??要注意的是,若该点为LCA且该点产生了贡献,贡献值应该-1,因为统计了两次
复杂度
O((n+m)logn)
代码
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#define Rint register int
#define Lint long long int
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=300010;
const int E=300005;
struct node
{int next,to;
}e[MAXN*2];
int head[MAXN],num;
int ls[MAXN*40],rs[MAXN*40];
int r1[MAXN*2],r2[MAXN*2];
int f[MAXN][20],dep[MAXN];
int siz[MAXN],son[MAXN];
int dfn[MAXN],top[MAXN];
int s[MAXN],t[MAXN];
int w[MAXN],c[MAXN];
int tag[MAXN*40];
int n,m,L,cnt;
int tot;
void add(int u,int v)
{e[++num]=(node){ head[u],v };head[u]=num;
}
void insert(Rint &k,Rint L,Rint R,Rint x)
{if( !k ) k=++tot;if( L==R ) return ;int mid=(L+R)/2;if( x<=mid ) insert( ls[k],L,mid,x );else insert( rs[k],mid+1,R,x );
}
void update(Rint k,Rint L,Rint R,Rint l,Rint r,Rint w)
{if( !k ) return ;if( L==l && R==r ) { tag[k]+=w;return ; }int mid=(L+R)/2;if( r<=mid ) update( ls[k],L,mid,l,r,w );elseif( l>=mid+1 ) update( rs[k],mid+1,R,l,r,w );else update( ls[k],L,mid,l,mid,w ),update( rs[k],mid+1,R,mid+1,r,w );
}
int query(Rint k,Rint L,Rint R,Rint x)
{if( !k || L==R ) return tag[k];if( tag[k] ){if( ls[k] ) tag[ls[k]]+=tag[k];if( rs[k] ) tag[rs[k]]+=tag[k];tag[k]=0;}int mid=(L+R)/2;if( x<=mid ) return query( ls[k],L,mid,x );else return query( rs[k],mid+1,R,x );
}
void init()
{for(int j=1;j<=L;j++)for(int i=1;i<=n;i++)f[i][j]=f[f[i][j-1]][j-1];
}
void Tarjan(int k)
{siz[k]=1;for(int i=head[k],x; i ;i=e[i].next){x=e[i].to;if( x==f[k][0] ) continue ;f[x][0]=k,dep[x]=dep[k]+1;Tarjan( x );siz[k]+=siz[x];if( siz[x]>siz[son[k]] ) son[k]=x;}
}
void dfs(int k)
{dfn[k]=++cnt;if( son[k] ) top[son[k]]=top[k],dfs( son[k] );for(int i=head[k],x; i ;i=e[i].next){x=e[i].to;if( x==f[k][0] || x==son[k] ) continue ;top[x]=x,dfs( x );}insert( r1[dep[k]+w[k]],1,n,dfn[k] );insert( r2[w[k]-dep[k]+E],1,n,dfn[k] );
}
int LCA(int x,int y)
{if( dep[x]<dep[y] ) swap( x,y );int delta=dep[x]-dep[y];for(int i=0;i<=L;i++)if( delta&(1<<i) ) x=f[x][i];for(int i=L;i>=0;i--)if( f[x][i]!=f[y][i] )x=f[x][i],y=f[y][i];if( x==y ) return x;else return f[x][0];
}
void work(int u,int v)
{int fa=LCA( u,v );int x=u;while( top[u]!=top[fa] ){update( r1[dep[x]],1,n,dfn[top[u]],dfn[u],1 );u=f[top[u]][0];}update( r1[dep[x]],1,n,dfn[fa],dfn[u],1 );while( top[v]!=top[fa] ){update( r2[dep[x]-2*dep[fa]+E],1,n,dfn[top[v]],dfn[v],1 );v=f[top[v]][0];}update( r2[dep[x]-2*dep[fa]+E],1,n,dfn[fa],dfn[v],1 );update( r1[dep[x]],1,n,dfn[fa],dfn[fa],-1 );
}
int main()
{int u,v;scanf("%d%d",&n,&m);L=log(n)/log(2)+1;for(int i=1;i<=n-1;i++){scanf("%d%d",&u,&v);add( u,v ),add( v,u );}for(int i=1;i<=n;i++) scanf("%d",&w[i]);Tarjan( 1 ),init();top[1]=1,dfs( 1 );for(int i=1;i<=m;i++){scanf("%d%d",&s[i],&t[i]);work( s[i],t[i] );}for(int i=1;i<=n;i++) c[i]=query( r1[dep[i]+w[i]],1,n,dfn[i] )+query( r2[w[i]-dep[i]+E],1,n,dfn[i] );for(int i=1;i<=n;i++) printf("%d ",c[i]);printf("\n");return 0;
}