[湖南集訓] 談笑風生
題意:
設 T 為一棵有根樹,我們做如下的定義:
• 設 a 和 b 為 T 中的兩個不同節點。如果 a 是 b 的祖先,那麼稱“a 比 b 不知道高明到哪裡去了”。
• 設 a 和 b 為 T 中的兩個不同節點。如果 a 與 b 在樹上的距離不超過某個給定常數 x,那麼稱“a 與 b 談笑風生”。
給定一棵 n 個節點的有根樹 T,節點的編號為 1 ∼ n,根節點為 1 號節點。你需要回答 q 個詢問,詢問給定兩個整數 p 和 k,問有多少個有序三元組 (a; b; c) 滿足:
1. a、 b 和 c 為 T 中三個不同的點,且 a 為 p 號節點;
2. a 和 b 都比 c 不知道高明到哪裡去了;
3. a 和 b 談笑風生。這裡談笑風生中的常數為給定的 k。
分析:
這道題目,也許是出題人有意地把題目描述複雜化了。實際上這並不是一道難題,甚至達不到近年來省選的難度。
但是這確實是一道值得交流的題目,我見過的這道題就有不下五種做法,有線上的,有離線的,我用的是線上的主席樹維護dfs序的方法。我開始以為這道題讓求的是這樣的有序三元組的數目,感覺十分不可做,後來發現,a點是已經固定的。
我們不考慮什麼“高明到哪裡去了”,三元組的要求是說,給出a,求點a和點b都在c到根的路徑上,也就是都是c的祖先,並且a和b距離不超過k的(a,b)數目。
因為a和b在c的父鏈上這個限制,我們可以分類討論,如果b也在a的父鏈上,那麼應該很好求,就是這條鏈上深度和a相差不超過k的點數目,乘上(a的子樹size-1)(減一是因為a自己不算),這就是一個簡單的乘法原理,預處理了O(1)就能求出。
然後是假如b本身就存在於a的一個子樹中,距離不超過k,我們知道,一棵子樹,在dfs序上是連續的,所以我們在預處理是,以dfs序為版本,以深度為區間,以子樹大小為權值建主席樹(原因是因為此時我只需要控制深度差範圍內的所有貢獻,並不一定要知道是具體哪個點的貢獻,所以深度一樣我們通通加到一個主席樹節點權值裡)。
查詢?很好說了吧,只要我們知道深度差的範圍和這個點對應的子樹dfs序區段。
程式碼:
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const主席樹int N=300005,inf=1e9; 5 struct node{int y,nxt;}e[N*2]; 6 int n,m,md,h[N],c=0,d[N],sz[N]; 7 int rt[N],cnt,tot,dfn[N],fa[N]; 8 struct ch{int l,r;ll da;}t[N*30]; 9 void add(int x,int y){ 10 e[++c]=(node){y,h[x]};h[x]=c; 11 e[++c]=(node){x,h[y]};h[y]=c; 12 } void dfs1(int x){sz[x]=1; 13 for(int i=h[x],y;i;i=e[i].nxt) 14 if(!d[y=e[i].y]){ 15 d[y]=d[x]+1;md=max(md,d[y]); 16 fa[y]=x;dfs1(y);sz[x]+=sz[y]; 17 } return ; 18 } void update(int &x,int l,int r,ll v,int p){ 19 t[++cnt]=t[x];x=cnt;t[x].da+=v; 20 if(l==r) return;int mid=l+r>>1; 21 if(p<=mid) update(t[x].l,l,mid,v,p); 22 else update(t[x].r,mid+1,r,v,p); 23 } ll query(int x,int y,int l,int r,int L,int R){ 24 if(L<=l&&r<=R) return t[y].da-t[x].da; 25 int mid=l+r>>1;ll re=0; 26 if(L<=mid)re+=query(t[x].l,t[y].l,l,mid,L,R); 27 if(mid<R)re+=query(t[x].r,t[y].r,mid+1,r,L,R); 28 return re; 29 } void dfs2(int x){ 30 dfn[x]=++tot;rt[tot]=rt[tot-1]; 31 update(rt[tot],1,md,sz[x]-1,d[x]); 32 for(int i=h[x],y;i;i=e[i].nxt){ 33 if((y=e[i].y)==fa[x]) continue; 34 dfs2(y); 35 } return ; 36 } int main(){ 37 scanf("%d%d",&n,&m); 38 for(int i=1,x,y;i<n;i++) 39 scanf("%d%d",&x,&y),add(x,y); 40 d[1]=1;dfs1(1);dfs2(1); 41 for(int i=1;i<=m;i++){ 42 int p,k;scanf("%d%d",&p,&k); 43 ll ans=(sz[p]-1)*1ll*(min(k,d[p]-1)); 44 int l=dfn[p]-1,r=dfn[p]+sz[p]-1; 45 if(d[p]==md){puts("0");continue;} 46 int z=min(md,d[p]+k); 47 ans+=query(rt[l],rt[r],1,md,d[p]+1,z); 48 printf("%lld\n",ans); 49 } return 0; 50 }