[BZOJ 2809] Dispatching

Link:https://www.lydsy.com/JudgeOnline/problem.php?id=2809

 

Algorithm:

很容易看出此题贪心的思路:

只要在每个点的子树中贪心选取费用最小的,使其总和不超过m即可

 

维护最小值,想到用堆,但普通的堆无法进行合并

于是用到数据结构可并堆/左偏树来在O(logN)的时间内合并堆

 

可并堆和左偏树的区别仅仅在于左偏树多维护了dist数组,而可并堆是无脑交换左右子树

这也使得左偏树的复杂度是能证明的O(logN),而可并堆仅仅是均摊复杂度为O(logN),因此还是尽量用左偏树吧

 

证明:n个节点的左偏树的距离dist最大为log(n+1)-1

若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。

设一棵左偏树的距离为k,则这棵左偏树至少有2^{k+1}-1个节点

因为n>=2^{k+1}-1,所以k<=log(n+1)-1

 

Code:

左偏树:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

inline int read()
{
    char ch;int num,f=0;
    while(!isdigit(ch=getchar())) f|=(ch=='-');
    num=ch-'0';
    while(isdigit(ch=getchar())) num=num*10+ch-'0';
    return f?-num:num;
}

const int MAXN=1e5+10;
vector<int> G[MAXN];
struct LTree
{
    int ls,rs,dist,val;
    ll siz,sum;
}Lt[MAXN];

int n,m,C[MAXN],L[MAXN],root[MAXN];
ll res=0;

int merge(int x,int y)
{
    if(!x || !y) return x+y;
    if(Lt[x].val<Lt[y].val) swap(x,y);
    Lt[x].rs=merge(Lt[x].rs,y);
    
    if(Lt[Lt[x].ls].dist<Lt[Lt[x].rs].dist) swap(Lt[x].ls,Lt[x].rs);
    Lt[x].dist=Lt[Lt[x].rs].dist+1;
    Lt[x].siz=Lt[Lt[x].rs].siz+Lt[Lt[x].ls].siz+1;
    Lt[x].sum=Lt[Lt[x].rs].sum+Lt[Lt[x].ls].sum+Lt[x].val;
    
    return x;
}

void pop(int &x)
{
    x=merge(Lt[x].ls,Lt[x].rs);
}

void dfs(int x)
{
    root[x]=x;
    for(int i=0;i<G[x].size();i++) 
        dfs(G[x][i]),root[x]=merge(root[x],root[G[x][i]]);
    while(Lt[root[x]].siz && Lt[root[x]].sum>m) pop(root[x]);
    res=max(res,L[x]*Lt[root[x]].siz);
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();G[x].push_back(i);
        C[i]=read();L[i]=read();
        Lt[i].sum=Lt[i].val=C[i];Lt[i].siz=1;
    }
    
    dfs(1);
    cout << res;
    return 0;
}

 

可并堆:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

inline int read()
{
    char ch;int num,f=0;
    while(!isdigit(ch=getchar())) f|=(ch=='-');
    num=ch-'0';
    while(isdigit(ch=getchar())) num=num*10+ch-'0';
    return f?-num:num;
}

const int MAXN=1e5+10;
vector<int> G[MAXN];

int n,m,C[MAXN],L[MAXN],root[MAXN];
ll res=0;

struct SkewHeap
{
    int v[MAXN],ls[MAXN],rs[MAXN];
    ll siz[MAXN],sum[MAXN];
    
    int merge(int x,int y)
    {
        if(!x || !y) return x+y;
        if(v[x]<v[y]) swap(x,y);
        rs[x]=merge(rs[x],y);
        swap(ls[x],rs[x]);
        
        sum[x]=sum[ls[x]]+sum[rs[x]]+v[x];
        siz[x]=siz[ls[x]]+siz[rs[x]]+1;
        return x;
    }
    
    void pop(int &x){x=merge(ls[x],rs[x]);}
}Heap;

void dfs(int x)
{
    root[x]=x;
    for(int i=0;i<G[x].size();i++)
        dfs(G[x][i]),root[x]=Heap.merge(root[x],root[G[x][i]]);
    while(Heap.siz[root[x]] && Heap.sum[root[x]]>m) Heap.pop(root[x]);
    res=max(res,L[x]*Heap.siz[root[x]]);
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();G[x].push_back(i);
        C[i]=read();L[i]=read();
        Heap.siz[i]=1;Heap.sum[i]=Heap.v[i]=C[i];
    }
    
    dfs(1);
    cout << res;
    return 0;
}

 

Review:

1、对于每棵左偏树,最好用root[x]记录其根节点

 

2、对于左偏树要跟着维护的信息,

     在merge的最后进行更新

 

3、可选择将整个数据结构封装在一个类中,写起来可能方便点

posted @ 2018-05-22 14:32  NewErA  阅读(183)  评论(0编辑  收藏  举报