2019-2020 XX Open Cup, Grand Prix of Korea J. Parklife 括号序列 树上启发式合并
题意
给定(x)轴上的(n)条除端点外互不相交的带权值线段
现在可以选择一些线段 求出 任意一条线段被覆盖次数不多于(i)次((1 leq i leq N))条件下的最大权值和
[1 leq N leq 250000\
1 leq S_i < E_i leq 1e6,1 leq V_i leq 1e9
]
分析
一般这种保证选择互不相交的条件都可以转化成树上问题 如果两个线段是包含关系转化为祖先关系,这样可以转化为类似括号序列的一棵树。问题相当于转化为树上选择一些顶点,满足任意一条链上的点不超过(i)个。
有比较显然的贪心:从(i)到(i+1)总是在原有基础上贪心加一些点。维护每次额外加的那些点,这可以用树形DP来做。
维护以(i)为根的子树的集合。显然,合并两颗子树的时候是可以直接加的(两子树对应的链独立),第(k)大的一定和第(k)的相加。这样就可以用启发式合并+优先队列实现了。
代码
const int maxn = 3e5 + 5;
struct seg{
int l,r,val;
friend bool operator < (const seg&a,const seg&b){
if(a.l == b.l) return a.r > b.r;
return a.l < b.l;
}
}a[maxn];
int n;
VI e[maxn];
VI v;
multiset<ll,greater<ll>> s[maxn];
void dfs(int u){
for(auto v:e[u]) {
dfs(v);
if(s[u].size() < s[v].size()) {
s[u].swap(s[v]);
}
multiset<ll,greater<ll>> tmp;
for(auto vv:s[v]) {
ll w = *(s[u].begin());
tmp.insert(w + vv);
s[u].erase(s[u].begin());
}
for(auto vv:tmp)
s[u].insert(vv);
}
if(u)
s[u].insert(a[u].val);
}
int main(){
n = rd();
VI ans(n + 5);
for(int i = 1;i <= n;i++)
a[i].l = rd(),a[i].r = rd(),a[i].val = rd();
a[0].l = -1e9,a[0].r =1e9;
sort(a,a + n + 1);
v.push_back(0);
for(int i = 1;i <= n;i++){
while(a[v.back()].r < a[i].r)
v.pop_back();
e[v.back()].push_back(i);
v.push_back(i);
}
dfs(0);
int p = 1;
for(auto it:s[0])
ans[p++] = it;
for(int i = 1;i <= n;i++){
ans[i] += ans[i - 1];
printf("%lld ",ans[i]);
}
}