zoukankan      html  css  js  c++  java
  • 洛谷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),故会觉得这道题与差分有点像吧。

  • 相关阅读:
    [置顶] 图书推荐:SQL Server 2012 T-SQL基础 Itzik Ben-Gan
    UVA1366-----Martian Mining------DP
    动态规划——最长公共子序列(LCS)
    需求分析挑战之旅(疯狂的订餐系统)(3)——背景-需要-需求规格
    JavaScript学习笔记(四十四) 装饰器
    C中的volatile用法
    Java注解Annotation学习
    非常好!讲逻辑回归的,讲得很透彻
    RPC的学习 & gprotobuf 和 thrift的比较
    僵尸进程学习 & 进程状态列表 & Linux信号学习
  • 原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/11791310.html
Copyright © 2011-2022 走看看