洛谷
题意:
一棵树,每个点有权值a与权值b。每个点的答案为该点的权值a乘子树(包括它自己)中选择的节点个数,其中要求选中的节点的权值和小于等于给定的M(仅一组询问)。问所有点的答案中最大的是多少。
解1:
如果单独对某个点做暴力询问,显然可以贪心选择权值b最小的节点。而这样的问题很方便在权值线段树上二分出答案。因此我们就得到了两种做法
主席树
是在主席树的题单里面看到这道题的,首先从主席树的角度考虑。由于这道题是子树中操作,显然应该利用dfs序先把树摊成序列。然后直接按照主席树的最经典应用做即可。
线段树合并
这种题主席树做法和线段树合并的做法通常是成对出现?对于每棵树的所有儿子做线段树启发式合并即可。
解2:
主席树的做法很显然,在想线段树合并做法时候发现直接可并堆做就好了,少写很多东西(thanks to pbds)(就算没有pbds,左偏树总也比主席树细节少)。每个点建一个大根堆,把儿子的堆都合并来,把自己插进去,维护每个堆的b权值和,如果和大于M就一直pop到和小于等于M。就做完了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const int maxn=111111; __gnu_pbds::priority_queue<int,less<int>,__gnu_pbds::pairing_heap_tag>h[maxn]; LL sum[maxn]; int n,m; int c[maxn],l[maxn]; LL ans=0; struct TREE { int nxt[maxn<<1],to[maxn<<1],first[maxn],cnt,fa[maxn]; void addedge(int u,int v) { nxt[++cnt]=first[u]; first[u]=cnt; to[cnt]=v; } void addedge2(int u,int v){addedge(u,v);addedge(v,u);} void dfs(int x) { for (int q=first[x];q;q=nxt[q]) if (to[q]!=fa[x]) { dfs(to[q]); h[x].join(h[to[q]]); sum[x]+=sum[to[q]]; } h[x].push(c[x]); sum[x]+=c[x]; while (sum[x]>m) { sum[x]-=h[x].top(); h[x].pop(); } ans=max(ans,(LL)((LL)l[x]*h[x].size())); } }tree; int main() { read(n,m); rep(i,1,n) { read(tree.fa[i],c[i],l[i]); if (i!=0) tree.addedge2(i,tree.fa[i]); } tree.dfs(1); printf("%lld\n",ans); return 0; }
|