題目傳送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 }
總結:blog
對於產生全局答案貢獻的處理方式:建全局桶。get
若想從一個存儲結構中獲得答案,應時刻維護結構以保證其存儲的數據的正確性。it
不寫出式子/畫出圖來,難以更深刻地優化或思考。
感受這道題跟差分的關係不大。只不過修改的複雜度與差分的區間修改都是O(1),大部分題目的修改複雜度很難有O(1),故會以爲這道題與差分有點像吧。