【BZOJ2288】[POJ Challenge]生日礼物(线段树)


  • 如果 m = 1 m=1 m=1 我们只需要求最大子段和即可。
  • 但是当 m > 1 m>1 m>1 时,我们的选取可能会产生后效性
  • 比如说黄色部分是选取一个子段的最优方案,橙色部分是选取两个子段的最优答案。
    在这里插入图片描述
  • 那么我们需要搞一个资瓷删除部分已经选过的数的操作。
  • 我们可以考虑直接把选出的部分取反,即区间取反操作。
  • 这里利用了类似网络流的退流思想。
  • 然后每次需要动态维护最大子段和。
  • 线段树实现即可。
  • 注意这里要求至多选取 m m m 个子段,若当前的最大子段和小于 0 0 0,那么接下来选取肯定不优,直接 break
  • 时间复杂度 O ( m log ⁡ n ) O(m\log n) O(mlogn)
#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	static bool opt; 
	while (!isdigit(ch = getchar()) && ch != '-'); 
	x = (opt = ch == '-') ? 0 : ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
	if (opt) x = ~x + 1; 
}

#define lc_info x << 1, l, mid
#define rc_info x << 1 | 1, mid + 1, r

const int MaxN = 1e5 + 5; 
const int MaxS = MaxN << 2; 
const int INF = 0x7fffffff; 

struct segment
{
	int l, r, s; 
	segment(){}
	segment(int a, int b, int c):
		l(a), r(b), s(c) {}
	
	inline bool operator < (const segment &rhs) const
	{
		return s < rhs.s; 
	}
	inline segment operator + (const segment &rhs) const
	{
		return segment(l, rhs.r, s + rhs.s); 
	}
}; 

template <class T>
inline void relax(T &x, const T &y)
{
	if (x < y) x = y; 
}

template <class T>
inline void tense(T &x, const T &y)
{
	if (y < x) x = y; 
}

int n, m; 
int a[MaxN], lef[MaxS], rit[MaxS]; 

int sum[MaxS]; 
bool tag[MaxS]; 
segment min_pre[MaxS], min_suf[MaxS], min_val[MaxS]; 
segment max_pre[MaxS], max_suf[MaxS], max_val[MaxS];

inline void neg(int &x)
{
	x = ~x + 1; 
}

inline void upt(int x)
{
	int mid = lef[x] + rit[x] >> 1; 
	
	sum[x] = sum[x << 1] + sum[x << 1 | 1]; 
	segment lc_seg = segment(lef[x], mid, sum[x << 1]); 
	segment rc_seg = segment(mid + 1, rit[x], sum[x << 1 | 1]); 
	
	max_pre[x] = max_suf[x] = max_val[x] = segment(0, 0, -INF); 
	min_pre[x] = min_suf[x] = min_val[x] = segment(0, 0, INF); 
	
	///
	
	relax(max_pre[x], max_pre[x << 1]); 
	relax(max_pre[x], lc_seg + max_pre[x << 1 | 1]); 
	
	relax(max_suf[x], max_suf[x << 1 | 1]); 
	relax(max_suf[x], max_suf[x << 1] + rc_seg); 
	
	relax(max_val[x], std::max(max_val[x << 1], max_val[x << 1 | 1])); 
	relax(max_val[x], max_suf[x << 1] + max_pre[x << 1 | 1]); 
	
	///
	
	tense(min_pre[x], min_pre[x << 1]); 
	tense(min_pre[x], lc_seg + min_pre[x << 1 | 1]); 
	
	tense(min_suf[x], min_suf[x << 1 | 1]); 
	tense(min_suf[x], min_suf[x << 1] + rc_seg); 
	
	tense(min_val[x], std::min(min_val[x << 1], min_val[x << 1 | 1])); 
	tense(min_val[x], min_suf[x << 1] + min_pre[x << 1 | 1]); 
}

inline void inv(int x)
{
	neg(sum[x]); 
	neg(max_pre[x].s), neg(min_pre[x].s), std::swap(max_pre[x], min_pre[x]); 
	neg(max_suf[x].s), neg(min_suf[x].s), std::swap(max_suf[x], min_suf[x]); 
	neg(max_val[x].s), neg(min_val[x].s), std::swap(max_val[x], min_val[x]); 
	tag[x] ^= 1; 
}

inline void dnt(int x)
{
	if (tag[x])
	{
		inv(x << 1); 
		inv(x << 1 | 1); 
		tag[x] = 0; 
	}
}

inline void build(int x, int l, int r)
{
	lef[x] = l, rit[x] = r; 
	max_pre[x] = max_suf[x] = max_val[x] = segment(l, r, -INF); 
	min_pre[x] = min_suf[x] = min_val[x] = segment(l, r, INF); 
	
	if (l == r)
	{
		sum[x] = a[l]; 
		max_pre[x] = max_suf[x] = max_val[x] = segment(l, r, a[l]); 
		min_pre[x] = min_suf[x] = min_val[x] = segment(l, r, a[l]); 
		return; 
	}
	
	int mid = l + r >> 1; 
	build(lc_info); 
	build(rc_info); 
	
	upt(x); 
}

inline void modify(int x, int l, int r, int u, int v)
{
	if (u <= l && r <= v)
		return (void)(inv(x)); 
	dnt(x); 
	
	int mid = l + r >> 1; 
	if (u <= mid)
		modify(lc_info, u, v); 
	if (v > mid)
		modify(rc_info, u, v); 
	
	upt(x); 
}


int main()
{
	read(n), read(m); 
	for (int i = 1; i <= n; ++i)
		read(a[i]); 
	build(1, 1, n); 
	
	int ans = 0; 
	for (int i = 1; i <= m; ++i)
	{
		segment res = max_val[1]; 
		if (res.s < 0) break;
		ans += res.s; 
		modify(1, 1, n, res.l, res.r); 
	}
	printf("%d\n", ans); 
	
	return 0;  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值