BZOJ3252 攻略 [樹鏈剖分]
阿新 • • 發佈:2018-11-09
攻略
Submit: 1169 Solved: 554
[Submit][Status][Discuss]
Description
題目簡述:樹版[k取方格數] 眾所周知,桂木桂馬是攻略之神,開啟攻略之神模式後,他可以同時攻略k部遊戲。今天他得到了一款新遊戲《XX 半島》,這款遊戲有n個場景(scene),某些場景可以通過不同的選擇支到達其他場景。所有場景和選擇支構成樹狀 結構:開始遊戲時在根節點(共通線),葉子節點為結局。每個場景有一個價值,現在桂馬開啟攻略之神模式,同Input
第一行兩個正整數n,k 第二行n個正整數,表示每個場景的價值 以下n-1行,每行2個整數a,b,表示a場景有個選擇支通向b場景(即a是b的父親) 保證場景1為根節點 n<=200000,1<=場景價值<=2^31-1Output
輸出一個整數表示答案Sample Input
5 24 3 2 1 1
1 2
1 5
2 3
2 4
Sample Output
10HINT
分析:
網上大部分人好像都是用的$dfs$序+線段樹,實際上不用那麼麻煩,直接上樹鏈剖分,加個貪心就行了。
對於這道題,肯定要優先選取從起點(不一定是根,因為走過的路徑不能重複算,所以有的路徑走過以後會影響其他路徑)到葉子節點權值和最大的路徑,這顯然就是樹鏈剖分的輕重鏈/長短鏈(廣義上)的思想,不過這裡把剖分的標準換成權值和就行了。
剖分完以後,$top[]$值等於自身的節點就是一條路徑的起點,我們把它的權值和丟進大根堆裡維護然後取前$k$大就行了。
Code:
//It is made by HolseLee on 9th Nov 2018 //BZOJ 3252 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e5+7; int n,m,head[N],cnte,fa[N],hson[N],top[N]; ll a[N],siz[N],ans; struct Edge { int to,nxt; }e[N<<1]; priority_queue<ll>q; inline ll read() { char ch=getchar(); ll x=0; bool flag=false; while( ch<'0' || ch>'9' ) { if( ch=='-' ) flag=true; ch=getchar(); } while( ch>='0' && ch<='9' ) { x=x*10+ch-'0'; ch=getchar(); } return flag ? -x : x; } inline void add(int x,int y) { e[++cnte].to=y; e[cnte].nxt=head[x]; head[x]=cnte; } void dfs1(int x) { siz[x]=a[x]; for(int i=head[x],y; i; i=e[i].nxt) { y=e[i].to; if( y==fa[x] ) continue; fa[y]=x; dfs1(y); siz[x]=max(siz[x],siz[y]+a[x]); if( siz[y]>siz[hson[x]] ) hson[x]=y; } } void dfs2(int x,int nowtop) { top[x]=nowtop; if( !hson[x] ) return; dfs2(hson[x],nowtop); for(int i=head[x],y; i; i=e[i].nxt) { y=e[i].to; if( y==fa[x] || y==hson[x] ) continue; dfs2(y,y); } } int main() { n=read(); m=read(); for(int i=1; i<=n; ++i) a[i]=read(); int x,y; for(int i=1; i<n; ++i) { x=read(), y=read(); add(x,y), add(y,x); } dfs1(1); dfs2(1,1); for(int i=1; i<=n; ++i) if( top[i]==i ) q.push(siz[i]); while( (m--) && (!q.empty()) ) { ans+=q.top(); q.pop(); } printf("%lld\n",ans); return 0; }