【BZOJ2809】【codevs1763】派遣,主席树记录前缀和

传送门1
传送门2
写在前面:祝自己生日快乐
思路:
题意简述为在一棵节点有特征值有费用的有根树上选定一个节点,在它的子树上找任意个节点使其总费用不超过m,要求权值最大(权值=该节点的特征值*选择的节点数)
比较暴力的思路是枚举每一个节点,从它的子树上依次选取费用从小到大的节点,直到超过费用或子树上全部节点都被选了,求出最大值即可,时间复杂度 O(n2)
将该思路精进一下,我们发现只要维护每个子树上的点费用从小到大,再加一个前缀和之类的东西,快速判断某一区间费用和是否超过费用即可就可以了
这就用到了主席树
这里的主席树不再是求区间第k大,因为它是权值线段树,节点即是费用(离散化后),所以在主席树上维护下前缀和,每次查询时判断[l,mid]是否超过限制,如果没有就加上左区间的点数,并到[mid+1,r]上去找,不然就继续到[l,mid]上找,每次查询的复杂度为 O(nlogn)
注意:
1.维护树上区间使用dfs序重新编号
2.离散化后的值仅是为了在主席树上使其有序,建树和查询时要用到的除了size还有费用前缀和sum
代码:

#include<bits/stdc++.h>
#define M 100004
#define LL long long
using namespace std;
int n,m,cnt,root,tot;
LL ans;
int fa[M],b[2][M],c[M],ID[M],first[M],Ls[M],Rs[M];//b[0]为离散化前的费用,b[1]为离散化后的费用,ID[i]记录离散化后为i的原费用
struct Chairman_tree
{
    int ch[2],siz;
    LL sum;
}a[M<<5];
struct disc
{
    int id,data;
    bool operator <(const disc other)const
    {
        return data<other.data;
    }
}d[M];
struct edge
{
    int u,v,next;
    void add(int x,int y)
    {
        u=x;v=y;
        next=first[x];
        first[x]=tot;
    }
}e[M];
int in()
{
    char ch=getchar();int t=0;
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) t=(t<<3)+(t<<1)+ch-48,ch=getchar();
    return t;
}
void dfs(int x)
{
    Ls[x]=++cnt;
    d[cnt].id=x;d[cnt].data=b[1][x];
    for (int i=first[x];i;i=e[i].next)
        dfs(e[i].v);
    Rs[x]=cnt;
}
void build(int now,int L,int R,int rt,int val)
{
    a[rt].siz=a[now].siz+1;
    a[rt].sum=a[now].sum+ID[val];
    if (L==R) return;
    int mid=(L+R)>>1;
    if (val<=mid)
        a[rt].ch[1]=a[now].ch[1],
        a[rt].ch[0]=++cnt,
        build(a[now].ch[0],L,mid,a[rt].ch[0],val);
    else
        a[rt].ch[0]=a[now].ch[0],
        a[rt].ch[1]=++cnt,
        build(a[now].ch[1],mid+1,R,a[rt].ch[1],val);
}
int get(int begin,int end,int L,int R,LL val)
{
    if (begin==end)
        if (ID[end]<=val) return a[R].siz-a[L].siz;
        else return 0;
    int mid=(begin+end)>>1;
    LL t=a[a[R].ch[0]].sum-a[a[L].ch[0]].sum;
    if (val>t)
        return a[a[R].ch[0]].siz-a[a[L].ch[0]].siz+get(mid+1,end,a[L].ch[1],a[R].ch[1],val-t);
    else return get(begin,mid,a[L].ch[0],a[R].ch[0],val);
}
main()
{
    n=in();m=in();
    for (int i=1;i<=n;i++)
    {
        fa[i]=in();
        if (fa[i]) e[++tot].add(fa[i],i);
        else root=i;
        b[0][i]=in(),
        d[i].id=i,d[i].data=b[0][i];
        c[i]=in();
    }
    sort(d+1,d+n+1);
    for (int i=1;i<=n;i++)
        b[1][d[i].id]=i,
        ID[i]=b[0][d[i].id];
    dfs(root);
    cnt++;
    for (int i=1;i<=n;i++) build(i,1,n,i+1,d[i].data);
    for (int i=1;i<=n;i++)
        ans=max(ans,(LL)c[i]*get(1,n,Ls[i],Rs[i]+1,m));
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值