Loading

2019-2020 XX Open Cup, Grand Prix of Korea J. Parklife 括号序列 树上启发式合并

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]);
	}
}
posted @ 2021-09-12 09:42  MQFLLY  阅读(99)  评论(0编辑  收藏  举报