gym102391J Parklife (2019-2020 XX Open Cup, Grand Prix of Korea) 啟發式合併
阿新 • • 發佈:2020-12-22
題意
有\(n\)個不交叉的線段,每個線段有一個權值。現選取一些線段,求每個小區間\([i,i+1]\)至多被覆蓋\(1-n\)次的最大權值和。
思路
- 由於線段互不相交,加上一個\([1,10^6]\)的權值為0的的線段,就可以構成一棵樹。
- 可以想到一個\(O(n^2)\)的\(dp\),\(dp[u][i]\)代表u這顆子樹最多被覆蓋\(i\)次的答案,有狀態轉移方程\(dp[u][i]=max\{w_u+\sum dp[v][i-1],\sum dp[v][i]\}\)。
- 令\(f_u(i)=dp[u][i]\),可以發現\(f\)是單調不降函式,且是上凸函式。考慮不取\(u\)的情況,則\(f_{v1}+f_{v2}\)
- 可以用堆維護\(dp\)值的差分,可以發現\(f_{v1}+f_{v2}\)的差分值就是差分由大到小對應位置相加,\(f_u(i)=max\{f_u(i),f_u(i-1)+w_u\}\)可以看成把\(w_u\)插入堆中。其中堆合併可以把小的堆併入大的堆中,啟發式合併複雜度為\(O(nlog^2n)\)。
- 其真正複雜度為\(O(nlogn)\)的。對於每個節點,都會向堆中插入一個新的數。對於每次堆合併,都是把小的堆的所有節點刪除,總大小還是大的堆的大小,沒有增加新的元素。所以最多插入\(O(n)\)
程式碼
#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; }