题目:洛谷P1600、BZOJ4719、Vijos P2004、UOJ#261。
题目大意:一些人在一棵树上,每秒移动一个单位,从起点移动到终点。每个节点上有一个观测员,第i个能观测到w[i]秒刚好到这个节点的人。问每个观测员能观测到几个人。
解题思路:NOIP2016提高最难的一题啊!虽说80分很容易拿,但想AC却真的很难!w(゚Д゚)w
做法基本就这么两种:①树链剖分;②LCA+树上差分。
树剖我掌握得并不熟练(我仅用它写过LCA而且已经忘了),所以我只能用②了。
先讲思路。首先,对于每个人走的路径s->t,可拆分为s->lca(s,t)和lca(s,t)->t。我们对其分别考虑。
对于s->lca(s,t)之间任何一个点i,它能观测到当前这个人必须满足$deep[s]-w[i]=deep[i]$(其中deep[i]表示节点i到根节点的深度,下同),移项得$deep[s]=deep[i]+w[i]$,这样左边只和s有关,右边只和i有关。
再来考虑lca(s,t)->t之间任何一个点i,它能观测到当前这个人必须满足$deep[t]-deep[i]=len-w[i]$(len表示当前这个人的路径的总长度),移项得$deep[t]-len=deep[i]-w[i]$,这样左边只和t有关,右边只和i有关。
然后分别对两种路径进行差分。dfs遍历树,然后当前节点为i,我们用一个桶(tong)统计。tong的下标即为等式右边的表达式(用i即可算出),记录的即为等式左边的表达式(统计答案)。
由于$deep[t]-len=deep[i]-w[i]$中,等式左、右可能出现负数,只需把桶的下标统一往后移300000即可(Pascal请忽略)。
最后,如果一个人在lca上被统计到,那么它在s->lca(s,t)和lca(s,t)->t中都被统计了一遍,需要减去。
时间复杂度:Tarjan求LCA则$O(n+m)$,倍增等求LCA则$O(m\log_2 n)$,差分是$O(n)$的。我选用速度较快的Tarjan求LCA。
为方便大家(我)理解,我在代码核心部分增添了一些注释。
C++ Code:
#include<cstdio>
#include<cstring>
#include<vector>
#include<cctype>
using namespace std;
#define C c=getchar()
#define N 300010
int n,m,ne=0,nq=0,maxdeep=0;
bool vis[N];
int f[N],head[N],que[N],w[N],deep[N],mk[N],ans[N],tong[N<<1];
vector<int>js[N],js2[N],js3[N];//用于差分。由于直接开数组会炸,而实际要用的空间不多,所以用vector
struct query{
int same,nxt,to,num;
bool flag;
}q[N<<1];
struct edge{
int to,nxt;
}e[N<<1];
struct men{
int x,y,len,lca;
}a[N];
inline int readint(){
char C;
for(;!isdigit(c);C);
int d=0;
for(;isdigit(c);C)
d=(d<<3)+(d<<1)+(c^'0');
return d;
}
inline void add_edge(int x,int y){
e[++ne].to=y;
e[ne].nxt=head[x];
head[x]=ne;
e[++ne].to=x;
e[ne].nxt=head[y];
head[y]=ne;
}
inline void add_que(int x,int y,int z){
q[++nq].to=y;
q[nq].same=nq+1;
q[nq].num=z;
q[nq].nxt=que[x];
que[x]=nq;
q[++nq].to=x;
q[nq].same=nq-1;
q[nq].num=z;
q[nq].nxt=que[y];
que[y]=nq;
}
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
void tarjan(int root){
for(int i=head[root];i;i=e[i].nxt){
int v=e[i].to;
if(deep[v])continue;
if(maxdeep<(deep[v]=deep[root]+1))maxdeep=deep[v];//求每个点到根节点的深度和树的最大深度
tarjan(v);
f[v]=root;
vis[v]=true;
}
for(int i=que[root];i;i=q[i].nxt)
if(vis[q[i].to]&&!q[i].flag){
int p=q[i].num;
a[p].len=deep[a[p].x]+deep[a[p].y]-(deep[a[p].lca=find(q[i].to)]<<1);//顺便算出每个人跑的总长度
q[i].flag=q[q[i].same].flag=true;
}
}//Tarjan求LCA
void dfs1(int rt){//deep[s]=deep[i]+w[i]
int now=deep[rt]+w[rt],pre;//now记录等式右边,tong统计等式左边。pre记录开始时的tong[now]
if(now<=maxdeep)pre=tong[now];//当now超过最大深度则无需计算
for(int i=head[rt];i;i=e[i].nxt)
if(deep[rt]<deep[e[i].to])dfs1(e[i].to);
tong[deep[rt]]+=mk[rt];
if(now<=maxdeep)ans[rt]+=tong[now]-pre;//当now超过最大深度则无需计算,否则更新答案
//由于tong[now]在深搜过程中已经变化,那么变化的值就是可被观测到的人数。
for(int i=js[rt].size()-1;i>=0;--i)--tong[js[rt][i]];//把lca为当前节点的减去
}
void dfs2(int rt){//deep[t]-len=deep[i]-w[i]
int now=deep[rt]-w[rt]+300000,pre;//now、tong、pre同上
pre=tong[now];
for(int i=head[rt];i;i=e[i].nxt)
if(deep[rt]<deep[e[i].to])dfs2(e[i].to);
for(int i=js2[rt].size()-1;i>=0;--i)++tong[js2[rt][i]+300000];//加上等式左边的东西
ans[rt]+=tong[now]-pre;//原理同上
for(int i=js3[rt].size()-1;i>=0;--i)--tong[js3[rt][i]+300000];//把无用节点减去
}
int main(){
n=readint(),m=readint();
memset(vis,0,sizeof vis);
memset(deep,0,sizeof deep);
memset(ans,0,sizeof ans);
deep[1]=1;
for(int i=1;i<n;i++){
int u=readint(),v=readint();
add_edge(u,v);
}
for(int i=1;i<=n;++i)w[f[i]=i]=readint();
for(int i=1;i<=m;i++){
a[i].x=readint();
a[i].y=readint();
if(a[i].x!=a[i].y)
add_que(a[i].x,a[i].y,i);else{
a[i].len=0;
a[i].lca=a[i].x;
}
}//保存LCA询问,Tarjan
tarjan(1);
memset(tong,0,sizeof tong);
memset(mk,0,sizeof mk);//mk[i]表示第i个节点作为起点多少次
for(int i=1;i<=m;++i){
++mk[a[i].x];
js[a[i].lca].push_back(deep[a[i].x]);//js[i]存储lca为i的人的起点的深度
}
dfs1(1);//处理s->lca(s,t)路径
memset(tong,0,sizeof tong);
for(int i=1;i<=m;++i){
int f=deep[a[i].y]-a[i].len;
js2[a[i].y].push_back(f);//js2[i]存储终点为i的人的终点的深度减去这个人走的总长
js3[a[i].lca].push_back(f);//js3[i]存储lca为i的人的终点的深度减去这个人走的总长
//这两个数组用来差分
}
dfs2(1);
for(int i=1;i<=m;++i)
if(deep[a[i].x]-deep[a[i].lca]==w[a[i].lca])--ans[a[i].lca];
//当lca(s,t)正好可以观测到这个人时,它在s->lca(s,t)和lca(s,t)->t都会被统计一次,所以去重。
for(int i=1;i<n;++i)printf("%d ",ans[i]);
printf("%d\n",ans[n]);
return 0;
}
在BZOJ上PE是什么鬼!?