1. 程式人生 > >[樹上差分][子樹求和][樹形dp] Jzoj P5911 Travel

[樹上差分][子樹求和][樹形dp] Jzoj P5911 Travel

Description

        EZ同學家裡非常富有,但又極其的謙虛,說話又好聽,是個不可多得的人才。
        EZ常常在假期環遊世界,他準備去N(N<=100000)個國家之多,一些國家有航線連線,由於EZ同學有一定的強迫症,任意兩個國家之間都能通過航路直接或間接到達,並且這樣的路徑僅有一種。(簡單來說,這些國家構成了一棵樹)
         由於EZ是C國人,因此將C國(1號國家)作為整棵樹的根
        每個國家有一個旅遊熱度A[i]和影響力D[i]。由於目的地有點多,為了避免選擇困難症,他給每個國家設定了一個嚮往值F[i],它等於所有的A[j]之和,滿足i國在j國向C國走D[j]步的路徑上(經過一條航路算一步,i=j也會被統計,如果D[j]步超過了C國,則超出部分不用管)。
         LYD同學家裡有礦,富有程度與EZ不相上下,但他卻在宅與現充間搖擺不定。某次機緣巧合,EZ外出旅遊刺激了LYD,他決定也要開始旅遊。為了避免又被判高重複率導致被取消資格,他將EZ的旅遊地圖略微做了一點調整,每條航路將有一定的概率出現。
         現在他有Q個詢問,每次詢問某個國家所在的聯通塊(由於每條邊是一定概率出現,因此它所在的聯通塊可以是很多種)中所有國家的F[i]值的和的平方的期望(對998244353取模),以此來決定他旅遊的目的地。但他極其厭惡繁瑣的計算,於是他找到了能算出圓周率並將它倒背下來的你,答應給你豐厚的報酬。家裡沒礦,老爸也不是X達集團老總的你決定接受他的任務。
 

Input

第一行1個正整數N,表示國家數。
接下來N行,第i行兩個非負整數A[i],D[i],表示國家i的旅遊熱度以及影響力
接下來N-1行,每行三個非負整數x,y,v,x,y為這條航路連線的兩個國家,v為這條航路出現的概率。(注意每個在EZ的地圖中是沒有出現概率的說法的,因此每個國家的F值與邊的出現概率無關)
接下來一行一個正整數Q,表示詢問數
接下來Q行,每行一個正整數x,表示詢問國家x所在的聯通塊中所有國家的F[i]值的和的平方的期望(對998244353取模)。

Output

Q行,每行一個非負整數表示這次詢問的答案。
 

Sample Input

Sample Input1:
5
2 1
1 0
3 1
3 2
1 4
1 2 1
1 3 1
2 4 1
2 5 0
3
1
3
5

Sample Input2:
4
5 1
3 0
10 2
4 1
1 2 74017368
1 3 59531864
2 4 25036401
3
4
2
3

 

Sample Output

Sample Output1:
400
400
1

樣例解釋: 
可算出各國的F值分別為9,5,3,3,1
1,2,3,4必定在同一個聯通塊中,5在另一個聯通塊
1,2,3,4所在聯通塊F值和為20,因此答案是400
5所在的聯通塊F值和為1,因此答案是1

Sample Output2:
988451137
606447316
733454972
樣例解釋: 
可算出各國的F值分別為15,7,10,4
根據期望的定義,可計算出答案
 
 

Data Constraint


對於100%的資料,1<=N,Q<=200000,0<=A[i],v<998244353,0<=D[i]<N
 
 

Hint

 

 

題解

  • 剛看到題目的時候一臉懵逼,那按流程來
  • 題目大意:給出一棵樹,每個節點有兩個值 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