【bzoj2809】dispatching 斜堆

       一开始觉得是点分治,后来发现不对,因为这棵树有明显的父子关系,是不能颠倒的。。 然后发现都写的是可并堆。。然后又发现斜堆好短啊。。(后来发现斜堆好像是左偏树的弱(jian)化(hua)版?)

       实际上,斜堆的操作非常非常简单,比一般的堆还要简单(那还学什么一般的堆,搞得我学过一样),因为它就一个操作,合并merge:

       如果将x,y合并,首先判断键值大小,不妨设在大根堆中,且val[x]>val[y](否则交换x,y),那么就先将x的右节点和y合并(递归操作),然后交换x的左右结点(在左偏树中,还要判断左右结点的一个高度(到最近的叶子节点的距离),然后更新x的高度,但在斜堆中直接省略)。时间复杂度均摊O(logN)。

       接下来:查询,直接调用根节点,O(1);在堆x中插入,就当成x和一个节点为1的堆合并;删除节点x,就当成合并x的左右子节点然后替换x。

       因此在这道题目中,我们用dfs进行,显然正常人都会选择薪水少的。那么到操作到x时,对于x保存一个大根堆,保证大根堆内节点的键值总和<=M。更新时先把x的子节点的堆合并到一起变成x,然后把根节点一个一个删掉,保证薪水总和<=M,然后更新答案就行了。

       显然每一条边对应一次合并操作,而每个点最多被删除1次,因此总时间复杂度O(NlogN)。

AC代码如下(是不是很短呢>_<):

<span style="font-family:SimSun;font-size:18px;">#include<iostream>
#include<cstdio>
#define ll long long
#define N 200005
using namespace std;

int n,tot,fst[N],pnt[N],nxt[N],rt[N],f[N]; ll c[N],m,ans=0;
struct node{ int l,r,sz; ll sum; }a[N];
int merge(int x,int y){
	if (!x || !y) return x+y;
	if (c[x]<c[y]) swap(x,y);
	a[x].r=merge(a[x].r,y); swap(a[x].l,a[x].r); return x;
}
void add(int aa,int bb){
	pnt[++tot]=bb; nxt[tot]=fst[aa]; fst[aa]=tot;
}
void dfs(int x){
	a[x].sum=c[x]; a[x].sz=1; rt[x]=x; int p;
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p]; dfs(y);
		a[x].sum+=a[y].sum; a[x].sz+=a[y].sz;
		rt[x]=merge(rt[x],rt[y]);
	}
	while (a[x].sum>m){
		a[x].sum-=c[rt[x]]; a[x].sz--;
		rt[x]=merge(a[rt[x]].l,a[rt[x]].r);
	}
	ans=max(ans,(ll)f[x]*a[x].sz);
}
int main(){
	scanf("%d%lld",&n,&m); int i;
	for (i=1; i<=n; i++){
		int x; scanf("%d%lld%d",&x,&c[i],&f[i]);
		if (x) add(x,i);
	}
	dfs(1); printf("%lld\n",ans);
	return 0;
}
</span>

by lych

2016.2.2

你可能感兴趣的:(DFS,斜堆)