洛谷P1600 每天愛跑步——題解

題目傳送node

 

 

 

   首先要考慮入手點。先考慮一個一個玩家處理,顯然不加優化的話,時間複雜度是O(n)的。發現對於玩家路徑上的點都有一個觀察員,一個都不能忽視,看起來是很難優化了。在作題時,發現一個思路很難想,就應該考慮一下換個角度思考。OI中如此,生活亦是如此。ios

  那就嘗試從觀察員入手。對於每一個玩家,他們在樹上的路徑必定能分爲向上和向下的兩段(分的段長度能夠爲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只能在該路徑向下走的另半段。  git

  考慮怎麼尋找答案,顯然對每一個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個桶裏相應下標處找答案就好了。ide

  但這裏有一個問題,即桶裏記錄的路徑條數所包含的路徑中,有可能有路徑是不通過u的。這好辦,能夠從根dfs,當dfs到某個路徑的起點時入一下第一個桶、終點時入一下第二個桶,等到回溯到這條路徑的最高點(即這條路徑的ca)時讓先前入的桶的相應位置處分別--(出桶)就行了。優化

  還有一個問題,即若是隻經過上面的操做的話,仍是可能會碰到桶裏還記錄着這條路徑,但這條路徑仍沒通過u,這時只有一種可能,就是這條路徑的起點或終點在u的兄弟的子樹中,但ca在u的上面。u的答案應該是遍歷完以u爲根的子樹後桶內的相應下標處的增量,即遍歷完以u爲根的子樹後,2個桶會有一些下標處的值發生了變化,這些變化正是以u爲根的子樹對桶的影響,而只有這些影響量纔多是u的答案,由於這些影響量其實就是在u的子樹中的路徑起點或終點入桶、ca在u的子樹中的路徑出桶形成的結果,別忘了只有路徑起點或終點在u的子樹中,且ca爲u或是u的祖先的路徑才能對u產生貢獻。而這些影響量,就能夠經過記錄桶的增量來記錄。spa

代碼實現:code

  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註釋代碼

總結:blog

  對於產生全局答案貢獻的處理方式:建全局桶。get

  若想從一個存儲結構中獲得答案,應時刻維護結構以保證其存儲的數據的正確性。it

  不寫出式子/畫出圖來,難以更深刻地優化或思考。

  

感受這道題跟差分的關係不大。只不過修改的複雜度與差分的區間修改都是O(1),大部分題目的修改複雜度很難有O(1),故會以爲這道題與差分有點像吧。

相關文章
相關標籤/搜索