题目:洛谷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(mlog_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 ",ans[n]); return 0; }
在BZOJ上PE是什么鬼!?