zoukankan      html  css  js  c++  java
  • 【洛谷P1552】派遣

    题目

    题目链接:https://www.luogu.com.cn/problem/P1552

    在这个帮派里,有一名忍者被称之为 Master。除了 Master 以外,每名忍者都有且仅有一个上级。为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。

    现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者支付一定的薪水,同时使得支付的薪水总额不超过你的预算。另外,为了发送指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递人。管理者自己可以被派遣,也可以不被派遣。当然,如果管理者没有被排遣,你就不需要支付管理者的薪水。

    你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平,其中每个忍者的领导力水平也是一定的。

    写一个程序,给定每一个忍者 \(i\) 的上级 \(B_i\),薪水 \(C_i\),领导力 \(L_i\),以及支付给忍者们的薪水总预算 \(M\),输出在预算内满足上述要求时顾客满意度的最大值。

    思路

    众所周知这是一道左偏树的模板题。所以我们考虑线段树。

    习惯性,设 \(a_i\) 表示薪水,\(b_i\) 表示领导力。

    考虑枚举每一个点 \(x\) 作为管理者,那么显然为了使满意度最大,我们要在 \(x\) 为根的子树中选择尽量多的点,同时满足 \(\sum_{y} a_y\leq m\)

    所以肯定选择薪水最小的若干个。到这里左偏树的思路已经完全出来了。

    不会左偏树怎么办?其实可以用权值线段树解决。设 \(rk[i]\) 表示第 \(i\) 个忍者的薪水是第几小的,对于一个点 \(x\),如果我们可以把位于 \(x\) 的子树里的点插入进以 \(rk\) 为下标的权值线段树,那么就可以轻松的求出最多能选择多少个忍者。

    如何维护这 \(n\) 个线段树呢?直接从下往上权值线段树合并即可。

    时间复杂度 \(O(n\log n)\)

    代码

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define mp make_pair
    using namespace std;
    typedef long long ll;
    
    const int N=100010;
    int n,m,tot,root,head[N],rt[N],a[N],b[N],rk[N];
    ll ans;
    priority_queue<pair<int,int> > q;
    
    struct edge
    {
    	int next,to;
    }e[N];
    
    void add(int from,int to)
    {
    	e[++tot].to=to;
    	e[tot].next=head[from];
    	head[from]=tot;
    }
    
    struct SegTree
    {
    	int tot,lc[N*40],rc[N*40],cnt[N*40];
    	ll sum[N*40];
    	
    	void pushup(int x)
    	{
    		sum[x]=sum[lc[x]]+sum[rc[x]];
    		cnt[x]=cnt[lc[x]]+cnt[rc[x]];
    	}
    	
    	int update(int x,int l,int r,int k,int val)
    	{
    		if (!x) x=++tot;
    		if (l==k && r==k)
    		{
    			sum[x]=val; cnt[x]=1;
    			return x;
    		}
    		int mid=(l+r)>>1;
    		if (k<=mid) lc[x]=update(lc[x],l,mid,k,val);
    			else rc[x]=update(rc[x],mid+1,r,k,val);
    		pushup(x);
    		return x;
    	}
    	
    	int merge(int x,int y)
    	{
    		if (!x || !y) return x+y;
    		lc[x]=merge(lc[x],lc[y]);
    		rc[x]=merge(rc[x],rc[y]);
    		pushup(x);
    		return x;
    	}
    	
    	ll query(int x,int l,int r,int k)
    	{
    		if (!x) return 0;
    		if (k>=sum[x]) return cnt[x];
    		int mid=(l+r)>>1;
    		if (sum[lc[x]]>k) return query(lc[x],l,mid,k);
    			else return query(rc[x],mid+1,r,k-sum[lc[x]])+cnt[lc[x]];
    	}
    }seg;
    
    void dfs(int x)
    {
    	for (int i=head[x];~i;i=e[i].next)
    	{
    		int v=e[i].to;
    		dfs(v);
    		rt[x]=seg.merge(rt[x],rt[v]);
    	}
    	rt[x]=seg.update(rt[x],1,n,rk[x],a[x]);
    	ans=max(ans,1LL*b[x]*seg.query(rt[x],1,n,m));
    }
    
    int main()
    {
    	memset(head,-1,sizeof(head));
    	scanf("%d%d",&n,&m);
    	for (int i=1,fa;i<=n;i++)
    	{
    		scanf("%d%d%d",&fa,&a[i],&b[i]);
    		if (!fa) root=i;
    			else add(fa,i);
    		q.push(mp(-a[i],i));
    	}
    	for (int i=1;i<=n;i++)
    	{  
    		rk[q.top().second]=i;
    		q.pop();
    	}
    	dfs(root);
    	printf("%lld",ans);
    	return 0;
    }
    
  • 相关阅读:
    单例模式
    堆排序--leetcode 215
    二叉树--路径问题
    二叉树--前中后序两两结合构建二叉树
    CglibProxy
    JdkProxy
    git config --global http.sslVerify false
    PdfUtil
    idea中创建的文件类型无法识别
    sql优化
  • 原文地址:https://www.cnblogs.com/stoorz/p/12735857.html
Copyright © 2011-2022 走看看