洛谷P1600 天天爱跑步——题解

题目传送

   首先要考虑入手点。先考虑一个一个玩家处理,显然不加优化的话,时间复杂度是O(n)的。发现对于玩家路径上的点都有一个观察员,一个都不能忽视,看起来是很难优化了。在做题时,发现一个思路很难想,就应该考虑一下换个角度思考。OI中如此,生活亦是如此。

  那就尝试从观察员入手。对于每个玩家,他们在树上的路径一定能分为向上和向下的两段(分的段长度可以为0),设当前玩家x的路径的起点为st,终点为ed,st和ed的LCA为ca,路径长度为dis,当观察员u在玩家x向上走的那一段时,若玩家能在Wu秒时正好被观察员看到,那么一定有dep st-W u=dep u;当观察员u在玩家x从ca向ed走(即向下走)的那一段时,若玩家能在Wu秒时正好被观察员看到,那么一定有dep ed-dep u=dis-W u。因为我们要从观察员入手,所以我们将上面的两个式子中与u有关的移项到左边,无关的移项到右边,就得到了两个式子:1式:dep u+W u=dep st;2式:dep u+ W u=dis-dep ed。其实到这里就有一个显明的思路了:我们枚举每个观察员u,看一下有多少经过u的路径的dep st 等于dep u+W u、有多少经过u的路径的dis-dep ed等于dep u+ W u。这两个“多少”的和去重后(消去同一条路径同时满足两个条件从而计算2次带来的影响)就是观察员u能观察到的玩家数(答案)。为什么呢?其实若该路径的dep st 等于dep u+W u,那么该路径的st肯定不能比u还浅,那u肯定在这个路径向上走的半段;若该路径的dis-dep ed等于dep u+ W u,即dep ed-dep u=dis-W u,若u在该路径向上走的半段,这个式子显然不会成立(dis-Wu就是玩家走到u后再走到ed需要的时间,当u在路径的下半段时,dep ed-dep u正好就是从u走到ed所需的时间,若u在路径的上半段,dep ed-dep u只会比所需时间小),所以u只能在该路径向下走的另半段。  

  考虑怎么寻找答案,显然对每个u都暴力一遍所有的路径是不行的了。经过进一步的思考,发现若一条路径经过了u,那么它的起点st和终点ed至少有一个会在u的子树立,若st在u的子树里,那么u就在路径向上的半段;若ed在u的子树里,那么u就在路径向下的半段;特殊地,若st和ed都在u的子树里,那么u同时在路径向上和向下的半段,即u是这条路径的lca,此时应注意上文的去重。而对于一条路径,它可能会对多个u产生答案的贡献,但每次产生贡献时都有一个共同点,就是这条路径的depst 或 dis - dep ed 产生了一次相应的相等。由此可以考虑建2个全局的桶,分别记录dep st等于桶下标值的路径条数和 dis - dep ed 等于下标值的路径条数。但是对于dis - dep ed,发现它可能小于0,为了防止第二个桶的下溢出,第二个桶维护信息的意义应改为dis - dep ed+N 等于下标值的路径条数(N是一个较大的正整数,只要保证dis - dep ed+N恒非负,N可以在空间足够的情况下随便取)。对于每个观察员u,直接去2个桶里相应下标处找答案就行了。

  但这里有一个问题,即桶里记录的路径条数所包含的路径中,有可能有路径是不经过u的。这好办,可以从根dfs,当dfs到某个路径的起点时入一下第一个桶、终点时入一下第二个桶,等到回溯到这条路径的最高点(即这条路径的ca)时让先前入的桶的相应位置处分别--(出桶)就好了。

  还有一个问题,即如果只通过上面的操作的话,还是可能会碰到桶里还记录着这条路径,但这条路径仍没经过u,这时只有一种可能,就是这条路径的起点或终点在u的兄弟的子树中,但ca在u的上面。u的答案应该是遍历完以u为根的子树后桶内的相应下标处的增量,即遍历完以u为根的子树后,2个桶会有一些下标处的值发生了变化,这些变化正是以u为根的子树对桶的影响,而只有这些影响量才可能是u的答案,因为这些影响量其实就是在u的子树中的路径起点或终点入桶、ca在u的子树中的路径出桶造成的结果,别忘了只有路径起点或终点在u的子树中,且ca为u或是u的祖先的路径才能对u产生贡献。而这些影响量,就可以通过记录桶的增量来记录。

代码实现:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<queue>
  4 
  5 #define swap(x,y) (x^=y,y^=x,x^=y)
  6 
  7 using namespace std;
  8 
  9 const int N=300005,M=300005;
 10 
 11 int n,m,w[N],son[N],top[N],siz[N],f[N],lst[N];
 12 int nxt[N<<1],to[N<<1],cnt,dep[N],dis[M],ans[N];
 13 int t1[N],t2[N<<1],st[N];
 14 
 15 char ch;
 16 
 17 vector<int> lcau[N],lenv[N];
 18 
 19 struct node{
 20     int num,ord;
 21 };
 22 
 23 vector<node> lcav[N];
 24 
 25 inline int read()
 26 {
 27     int x=0;
 28     ch=getchar();
 29     while(!isdigit(ch)) ch=getchar();
 30     while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
 31     return x;
 32 }
 33 
 34 inline void addedge(int u,int v)
 35 {
 36     nxt[++cnt]=lst[u];
 37     lst[u]=cnt;
 38     to[cnt]=v;
 39 }
 40 
 41 void dfs1(int u,int fa)//处理f,dep,son,siz    树剖预处理第一遍dfs 
 42 {
 43     siz[u]=1;
 44     dep[u]=dep[fa]+1;
 45     int t;
 46     for(int e=lst[u];e;e=nxt[e])
 47         if((t=to[e])!=fa)
 48         {
 49             f[t]=u;
 50             dfs1(t,u);
 51             if(siz[t]>siz[son[u]])
 52                 son[u]=t;
 53             siz[u]+=siz[t];
 54         }
 55 }
 56 
 57 void dfs2(int u,int bos)//处理top    树剖预处理第二遍dfs 
 58 {
 59     top[u]=bos;
 60     if(son[u])
 61     {
 62         dfs2(son[u],bos);
 63         int t;
 64         for(int e=lst[u];e;e=nxt[e])
 65         {
 66             if((t=to[e])!=son[u]&&t!=f[u])
 67                 dfs2(t,t);
 68         }
 69     }
 70 }
 71 
 72 inline int lca(int x,int y)//树剖求LCA过程 
 73 {
 74     while(top[x]!=top[y])
 75     {
 76         if(dep[top[x]]>dep[top[y]])
 77             x=f[top[x]];
 78         else
 79             y=f[top[y]];
 80     }
 81     return dep[x]>dep[y]?y:x;
 82 }
 83 
 84 void dfs(int u)//找答案的dfs 
 85 {
 86     int aft=t1[dep[u]+w[u]]+t2[w[u]-dep[u]+N];//记录原来桶的量 
 87     for(int e=lst[u];e;e=nxt[e])
 88     {
 89         if(to[e]!=f[u])
 90             dfs(to[e]);
 91     }
 92     if(st[u])//入第一个桶 
 93     {
 94         t1[dep[u]]+=st[u];
 95     }
 96     if(lenv[u].size())//入第二个桶 
 97     {
 98         int l=lenv[u].size(),t;
 99         for(int i=0;i<l;++i)
100         {
101             t=lenv[u][i];
102             t2[t-dep[u]+N]++;
103         }
104     }
105     ans[u]=t1[dep[u]+w[u]]+t2[w[u]-dep[u]+N]-aft;//用当前桶的量减原来桶的量得到增量,得当前点u的答案 
106     if(lcau[u].size())//回溯到u的父亲后以u为ca的路径就没用了,要出桶 
107     {
108         int l=lcau[u].size(),x,y;
109         for(int i=0;i<l;++i)
110         {
111             x=lcau[u][i];
112             y=lcav[u][i].num;
113             if(dep[u]+w[u]==dep[x]&&w[u]-dep[u]==dis[lcav[u][i].ord]-dep[y])//去重 
114                 ans[u]--;
115             t1[dep[x]]--;
116             t2[dis[lcav[u][i].ord]-dep[y]+N]--;
117         }
118     }
119 }
120 
121 int main()
122 {
123     n=read(),m=read();
124     int u,v;
125     for(int i=1;i<n;++i)
126     {
127         u=read(),v=read();
128         addedge(u,v);
129         addedge(v,u);
130     }
131     for(int i=1;i<=n;++i)
132         w[i]=read();
133     dfs1(1,0);dfs2(1,1);
134     int ff,l;
135     for(int i=1;i<=m;++i)
136     {
137         u=read(),v=read();
138         ff=lca(u,v);//树链剖分求LCA 
139         st[u]++;//记录以u为路径起点的路径条数 
140         dis[i]=l=dep[u]+dep[v]-(dep[ff]<<1);
141         lcau[ff].push_back(u);//记录以ff为ca的路径的起点 
142         lcav[ff].push_back((node){v,i});//记录以ff为ca的路径的终点和编号 
143         lenv[v].push_back(l);//记录以v为路径的终点的路径长度 
144     }
145     dfs(1);
146     for(int i=1;i<=n;++i)
147     {
148         printf("%d ",ans[i]);
149     }
150     return 0;
151 }
AC注释代码

总结:

  对于产生全局答案贡献的处理方式:建全局桶。

  若想从一个存储结构中得到答案,应时刻维护结构以保证其存储的数据的正确性。

  不写出式子/画出图来,难以更深入地优化或思考。

  

感觉这道题跟差分的关系不大。只不过修改的复杂度与差分的区间修改都是O(1),大部分题目的修改复杂度很难有O(1),故会觉得这道题与差分有点像吧。

原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/11791310.html