一开始觉得是点分治,后来发现不对,因为这棵树有明显的父子关系,是不能颠倒的。。 然后发现都写的是可并堆。。然后又发现斜堆好短啊。。(后来发现斜堆好像是左偏树的弱(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