[樹上差分][子樹求和][樹形dp] Jzoj P5911 Travel
阿新 • • 發佈:2018-11-09
題解
- 剛看到題目的時候一臉懵逼,那按流程來
- 題目大意:給出一棵樹,每個節點有兩個值 Ai,Di,表示這個節點能給它的 0~Di 級祖先的 F 值 貢獻 Ai。然後給每條邊一個出現概率,每次詢問某個節點的聯通塊的 F 值的和的 平方的期望
- 首先考慮怎麼求f[i],由於是在i向上跳D[i]級都要加上這個值,當然暴力太慢,考慮一下樹上差分
- 在i點打上+1標記,然後再D[i]+1的祖先上打-1標記,就是子樹求和可以解決的了
- 考慮如何計算和的平方,這個完全平方公式顯然就是兩兩相乘再相加嘛,那麼就可以每次操作從詢問的國家開始dfs
- 用樹形dp來求解,如果現在又兩個要合併的聯通塊,值分別為a、b,然後成功的概率是p,答案:p(a+b)2+(1-p)a2=pa2+pb2+pab+a2-pa2=p(b+ab)+a2
- 這樣對於每個點i,維護以它為根的子樹中期望和的平方G[i],以及期望和H[i],按照上面從兒子合併到父親即可
- 一直合併到根就是當前詢問點的答案,這樣一次詢問的複雜度是O(N)的
- 那麼這樣的時間複雜度就是O(NQ),只有30分
- 考慮一下不用每次詢問都dfs的做法,我們不妨一直以1為根
- 那麼對於一個詢問i,只用知道i的子樹的G和H,和i以外部分的G和H
- 這樣的話就可以從父親的G和H和其他兄弟的G和H轉移過來,就可以了,就類似與一次換根操作
程式碼
1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 const int N=2e5+10,M=4e5+10,mo=998244353; 5 using namespace std; 6 struct edge {int to,from,v;}e[M]; 7 int a[N],d[N],head[N],n,Q,x,cnt,p[N],tot; 8 long long f[N],G[N],H[N],g[N],h[N]; 9 void insert(int x,int y,int v) { e[++cnt].to=y; e[cnt].from=head[x]; e[cnt].v=v; head[x]=cnt; } 10 void dfs1(int x,int fa) 11 { 12 int k=p[max(tot-d[x],0)]; 13 p[++tot]=x,f[x]+=a[x],f[k]-=a[x]; 14 for (int i=head[x];i;i=e[i].from) 15 if (e[i].to!=fa) dfs1(e[i].to,x),f[x]+=f[e[i].to]; 16 f[x]=(f[x]%mo+mo)%mo,p[tot--]=0; 17 } 18 void dfs2(int x,int fa) 19 { 20 g[x]=f[x],h[x]=f[x]*f[x]%mo; 21 for (int i=head[x];i;i=e[i].from) 22 if (e[i].to!=fa) 23 dfs2(e[i].to,x), 24 h[x]=(h[x]+(2*g[x]*g[e[i].to]+h[e[i].to])%mo*e[i].v%mo)%mo, 25 g[x]=(g[x]+g[e[i].to]*e[i].v)%mo; 26 } 27 void dp(int x,int fa) 28 { 29 for (int i=head[x];i;i=e[i].from) 30 if (e[i].to!=fa) 31 { 32 long long a=(G[x]-g[e[i].to]*e[i].v%mo+mo)%mo,b=(H[x]-(a*g[e[i].to]%mo*2%mo+h[e[i].to])%mo*e[i].v%mo+mo)%mo; 33 H[e[i].to]=(h[e[i].to]+(g[e[i].to]*a%mo*2+b)%mo*e[i].v%mo)%mo; 34 G[e[i].to]=(g[e[i].to]+a