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;
}

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/83143078
今日推荐