1. 程式人生 > >BZOJ 3252 攻略【貪心】【stl】

BZOJ 3252 攻略【貪心】【stl】

貪心的想一想,我們每次都選取權值之和最大的一條鏈。

m x [ i ] mx[i] 表示在以 i

i 為根的子樹中的最長鏈,我們可以通過遍歷一遍樹得到。由於我們總是要取權值之和最大的一條鏈,所以在遍歷樹的同時,將每個 m x [ i ] ,
i mx[i],i
的組合 p a i r pair 放進優先佇列裡面:

void dp(ll x) {
	for(ll i=head[x];i;i=nxt[i]) {
		ll y=to[i];
		dp(y);mx[x]=max(mx[x],mx[y]);
	}
	mx[x]+=v[x];q.push(make(mx[x],x));
}

接下來的問題是,我們不能每次都選取優先佇列中權值最大的那條鏈,因為這條鏈上的某些點的權值已經取過了,所以我們要考慮刪除這些影響。

注意到,我們每次選取合法最長鏈的時候,這條最長鏈的最上方的那個點的所有上方頂點一定已經在之前成為了之前某一條最長鏈的一部分,而我們不能重複取這些點的權值。

所以我們進行象徵性地刪除,即保證我們每次在優先佇列中取出的那條鏈的最上方點是沒有被選過的。

所以我們在每次選取最長鏈的時候(從第一次開始),將這條鏈刪除,而真正執行的刪除操作我們只需要用一個數組來記錄即可,具體說, v i s [ i ] vis[i] 表示 i i 號點能否作為一條最長鏈的起始點。

參考程式碼:

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define db double
#define sg string
#define ll long long
#define pll pair<ll,ll>
#define make make_pair
#define rep(i,x,y) for(ll i=(x);i<=(y);i++)
#define red(i,x,y) for(ll i=(x);i>=(y);i--)
using namespace std;

const ll N=2e5+5;
const ll Inf=1e18;

ll n,m,ans,f[N],v[N],mx[N],vis[N];
ll cnt,to[N<<1],nxt[N<<1],head[N];

priority_queue<pll>q;

inline ll read() {
    ll x=0;char ch=getchar();bool f=0;
    while(ch>'9'||ch<'0'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?-x:x;
}

void ins(ll x,ll y) {
	to[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt;
}

void dp(ll x) {
	for(ll i=head[x];i;i=nxt[i]) {
		ll y=to[i];
		dp(y);mx[x]=max(mx[x],mx[y]);
	}
	mx[x]+=v[x];q.push(make(mx[x],x));
}

void del(ll x) {
	vis[x]=1;
	for(ll i=head[x];i;i=nxt[i]) {
		ll y=to[i];
		if(mx[y]==mx[x]-v[x]) {
			del(y);break;
		}
	}
}

int main() {
	n=read(),m=read();
	
	rep(i,1,n) v[i]=read();
	
	rep(i,2,n) {
		ll x=read(),y=read();ins(x,y);
	}
	
	dp(1);
	
	while(m&&q.size()) {
		ll x=q.top().second;
		if(vis[x]) q.pop();
		else m--,ans+=mx[x],del(x);
	}
	
	printf("%lld\n",ans);

	return 0;
}