[BZOJ2809]dispatching(左偏树)

题面

http://darkbzoj.tk/problem/2809

题解

前置知识

可以先按上级-下级的关系建出一棵关系树,然后将在这个树上按dfs的顺序枚举题目中的“管理者”u。容易发现派遣的所有忍者都在u的子树中。而根据贪心思想,显然应该按照薪水从小到大选取u子树中的节点,直到即将超出预算。

对于每一个管理者,维护一个大根堆,里面存的是依照上述过程选出来的所有点的薪水值。那么点u的堆就是u的所有子节点的堆,以及C[u],这些东西全部合并;然后,每次删除最大值,直到堆中总和不超过预算。点u作为管理者对应的最佳答案就是此时堆的大小*L[u]。最后对所有u的答案求最大值即可。

合并堆的过程可以使用左偏树来实现。每个点最多入堆1次,出堆1次。合并总次数O(n)。故总时间复杂度(O(n log n))

代码

#include<bits/stdc++.h>

using namespace std;

#define ll long long 
#define In inline 
#define rg register

const ll N = 1e5;

In ll read(){
	ll s = 0,ww = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
	while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
	return s * ww;
}

ll C[N+5],L[N+5];

struct LftTree{
	ll cnt,c[N+5][2],val[N+5],dis[N+5],sum[N+5],num[N+5];
	void reset(){
		dis[0] = -1;
		cnt = 0;
	}
	int create(int i){
		cnt++;
		num[cnt] = 1;
		sum[cnt] = val[cnt] = C[i];
		return cnt;
	}		
	int merge(int u,int v){
		if(!u || !v)return u + v;
		if(val[u] < val[v])swap(u,v);
		c[u][1] = merge(c[u][1],v);
		if(dis[c[u][0]] < dis[c[u][1]])swap(c[u][0],c[u][1]);
		dis[u] = dis[c[u][1]] + 1;
		sum[u] = sum[c[u][0]] + sum[c[u][1]] + val[u];
		num[u] = num[c[u][0]] + num[c[u][1]] + 1;
		return u;
	}
	int pop(int u){
		return merge(c[u][0],c[u][1]);
	}
}T;

ll n,m,rt,ans;
ll head[N+5],cnt,root[N+5];

struct node{
	int des,next;
}e[N+5];

In void addedge(int a,int b){
	cnt++;
	e[cnt].des = b;
	e[cnt].next = head[a];
	head[a] = cnt;
}

void dfs(int u){
	root[u] = T.create(u);
	for(rg int i = head[u];i;i = e[i].next){
		int v = e[i].des;
		dfs(v);
		root[u] = T.merge(root[u],root[v]);
	}
	while(T.sum[root[u]] > m)root[u] = T.pop(root[u]);
	ans = max(ans,L[u] * T.num[root[u]]);
}

int main(){
	n = read(),m = read();
	T.reset();
	for(rg int i = 1;i <= n;i++){
		ll fa = read();
		if(!fa)rt = i;
		else addedge(fa,i);
		C[i] = read(),L[i] = read();
	}	
	dfs(rt);
	cout << ans << endl;
	return 0;
}
原文地址:https://www.cnblogs.com/xh092113/p/12346383.html