1. 程式人生 > 實用技巧 >gym102391J Parklife (2019-2020 XX Open Cup, Grand Prix of Korea) 啟發式合併

gym102391J Parklife (2019-2020 XX Open Cup, Grand Prix of Korea) 啟發式合併

題意

\(n\)個不交叉的線段,每個線段有一個權值。現選取一些線段,求每個小區間\([i,i+1]\)至多被覆蓋\(1-n\)次的最大權值和。

思路

  1. 由於線段互不相交,加上一個\([1,10^6]\)的權值為0的的線段,就可以構成一棵樹。
  2. 可以想到一個\(O(n^2)\)\(dp\)\(dp[u][i]\)代表u這顆子樹最多被覆蓋\(i\)次的答案,有狀態轉移方程\(dp[u][i]=max\{w_u+\sum dp[v][i-1],\sum dp[v][i]\}\)
  3. \(f_u(i)=dp[u][i]\),可以發現\(f\)是單調不降函式,且是上凸函式。考慮不取\(u\)的情況,則\(f_{v1}+f_{v2}\)
    還是單調不降的上凸函式。取\(u\)的的情況相當於取\(f_u\)和向量\((1,w_u)\)的閔可夫斯基和,結果同樣還是一個單調不降的上凸函式。
  4. 可以用堆維護\(dp\)值的差分,可以發現\(f_{v1}+f_{v2}\)的差分值就是差分由大到小對應位置相加,\(f_u(i)=max\{f_u(i),f_u(i-1)+w_u\}\)可以看成把\(w_u\)插入堆中。其中堆合併可以把小的堆併入大的堆中,啟發式合併複雜度為\(O(nlog^2n)\)
  5. 其真正複雜度為\(O(nlogn)\)的。對於每個節點,都會向堆中插入一個新的數。對於每次堆合併,都是把小的堆的所有節點刪除,總大小還是大的堆的大小,沒有增加新的元素。所以最多插入\(O(n)\)
    個元素,刪除\(O(n)\)個元素,複雜度為\(O(nlogn)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int maxn=2.5e5+5;

struct Edge{
    int v,next;
}edge[maxn*2];
int head[maxn],ecnt;
void add(int u,int v){
    edge[ecnt]={v,head[u]};
    head[u]=ecnt++;
    edge[ecnt]={u,head[v]};
    head[v]=ecnt++;
}

struct Node{
    int l,r;
    ll val;
}a[maxn];
priority_queue<ll>q[maxn];

void dfs(int u,int f)
{
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==f)continue;
        dfs(v,u);
        if(q[u].empty())
            swap(q[u],q[v]);
        else
        {
            vector<ll>tmp;
            if(q[u].size()<q[v].size())
                swap(q[u],q[v]);
            while(!q[v].empty())
            {
                tmp.push_back(q[v].top()+q[u].top());
                q[u].pop();
                q[v].pop();
            }
            for(ll val:tmp)
                q[u].push(val);
        }
    }
    q[u].push(a[u].val);
}

int main()
{
    ios::sync_with_stdio(false);
    int n;
    cin>>n;

    memset(head,-1,sizeof(head[0])*(n+5));
    ecnt=0;

    for(int i=0;i<n;i++)
        cin>>a[i].l>>a[i].r>>a[i].val;
    a[n].l=1;a[n].r=1e6;a[n].val=0;
    sort(a,a+n+1,[](Node x,Node y){
        if(x.l!=y.l)return x.l<y.l;
        else return x.r>y.r;
    });
    
    stack<int>stk;
    stk.push(0);
    for(int i=1;i<=n;i++)
    {
        int f=stk.top();
        while(a[i].l<a[f].l || a[i].r>a[f].r)
        {
            stk.pop();
            f=stk.top();
        }
        add(i,f);
        stk.push(i);
    }

    dfs(0,-1);
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        if(!q[0].empty())
        {
            ans+=q[0].top();
            q[0].pop();
        }
        cout<<ans<<(i==n?'\n':' ');
    }
    return 0;
}