Luogu P1552 [APIO2012]派遣【左偏树】By cellur925
$Chat$
哈哈哈我xj用dfs序乱搞竟然炸出了66分....(其实还是数据水,逃)
$Sol$
首先我们应该知道,一个人他自己的满意度与他子树所有节点的领导力是无关的,一个人的满意度受它子树影响只通过选子树的数量来体现。
因为薪水预算是有限的,而我们又想获得更多的子树,那么我们肯定想要子树薪水排名前$k$个的(满足不超过总预算)。我的暴力想法是每次排序来维护的,其实这里正解是用左偏树来维护的。(嘤我只写过左偏树板子而且不太理解)。
我们每次都尽量把一个节点的所有子树都选上,然后每次用可并堆找出花费最大的点干掉(即维护大根堆),直到满足条件。为什么要用可并堆呢?在这个树形结构中,根(最大的)被干掉后,我们要把它的左儿子和右儿子合并。这个时候用左偏树再合适不过了。
上述算法的实现用到了树形dp(递归)的思想。
$Code$
因为没写过非板子的左偏树题目...所以这里写清楚些。$fa[]$记录的是这个点的根。开始时这个节点的根等于它自己。$merge$函数返回的也是根。
1 #include<cstdio> 2 #include<algorithm> 3 #define maxn 100090 4 5 using namespace std; 6 typedef long long ll; 7 8 int n,m,tot; 9 int head[maxn],fa[maxn],dis[maxn],lch[maxn],rch[maxn],size[maxn]; 10 ll ans,v[maxn],val[maxn],sum[maxn]; 11 int T[maxn][2]; 12 struct node{ 13 int to,next; 14 }edge[maxn*2]; 15 16 void add(int x,int y) 17 { 18 edge[++tot].to=y; 19 edge[tot].next=head[x]; 20 head[x]=tot; 21 } 22 23 int merge(int x,int y) 24 { 25 if(x==0||y==0) return x+y; 26 if(v[x]<v[y]||(v[x]==v[y]&&x>y)) swap(x,y); 27 rch[x]=merge(rch[x],y); 28 fa[rch[x]]=y; 29 if(dis[lch[x]]<dis[rch[x]]) swap(lch[x],rch[x]); 30 dis[x]=dis[rch[x]]+1; 31 return x; 32 } 33 34 void dfs(int u,int f) 35 { 36 fa[u]=u;size[u]=1;sum[u]=v[u]; 37 for(int i=head[u];i;i=edge[i].next) 38 { 39 int v=edge[i].to; 40 if(v==f) continue; 41 dfs(v,u); 42 size[u]+=size[v];sum[u]+=sum[v]; 43 fa[u]=merge(fa[u],fa[v]); 44 } 45 while(sum[u]>m&&size[u]) 46 { 47 sum[u]-=v[fa[u]]; 48 size[u]--; 49 fa[u]=merge(lch[fa[u]],rch[fa[u]]); 50 } 51 ans=max(ans,1ll*size[u]*val[u]); 52 } 53 54 int main() 55 { 56 scanf("%d%d",&n,&m); 57 for(int i=1;i<=n;i++) 58 { 59 int x=0;scanf("%d",&x); 60 add(i,x);add(x,i); 61 scanf("%lld%lld",&v[i],&val[i]); 62 } 63 dfs(1,0); 64 printf("%lld",ans); 65 return 0; 66 }
独立意志与自由思想是必须争的,且须以生死力争。