您的位置:首页 > 其它

bzoj2809 dispatching 斜堆

2016-02-02 11:57 387 查看
一开始觉得是点分治,后来发现不对,因为这棵树有明显的父子关系,是不能颠倒的。。 然后发现都写的是可并堆。。然后又发现斜堆好短啊。。(后来发现斜堆好像是左偏树的弱(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
,pnt
,nxt
,rt
,f
; ll c
,m,ans=0;
struct node{ int l,r,sz; ll sum; }a
;
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: