【CF1668D】D. Optimal Partition(dp + 权值线段树)

TP

题意:

  • 给定一个数组 a,内部元素是有正有负的整数。定义选取一个连续段的价值是:

在这里插入图片描述
如果连续段的总和 sum 大于 0 ,价值为 长度;等于 0 价值为 0;小于 0,价值为长度的负数。

  • 你可以随意将 a 数组划分任意个连续段,要求最后的总和最大。

思路:

  • 一眼过去很正常的想到 dp,列出转移公式:
void solve(){
    for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i];
    for(int i=1;i<=n;i++){
        dp[i]=-1e9;
        for (int j=0;j<i;j++){//更新转移取MAX
            if (pre[i]-pre[j]>0)dp[i]=max(dp[i],dp[j]+(i-j));
            if (pre[i]==pre[j])dp[i]=max(dp[i],dp[j]);
            if (pre[i]-pre[j]<0)dp[i]=max(dp[i],dp[j]+(j-i));
        }
    }
    cout<<dp[n]<<endl;
}


可以发现只通过前缀和的三种状态转移。考虑对每个位置的前缀和值进行离散化排序,建立权值线段树维护 三种状态 的 max(dp值)

在这里插入图片描述
摘自 —— Snow的题解

C o d e : Code: Code:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define debug(x) cout<<"target is "<<x<<endl
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define oper(a) operator<(const a& ee)const
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const int N = 5e5 + 10, M = 100010, MM = 110;
int INF = 0x3f3f3f3f, mod = 998244353;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
ll a[N];
vector<ll> vec;
int dp[N];
ll find(ll x) {
	return lower_bound(all(vec), x) - vec.begin() + 1;
}

// 原本双重for循环dp通过前缀和 大于小于等于 0 三种状态转移
// 
// if (pre[i] - pre[j] > 0) dp[i] = max(dp[i], dp[j] + (i - j));
// if (pre[i] == pre[j]) dp[i] = max(dp[i], dp[j]);
// if (pre[i] - pre[j] < 0) dp[i] = max(dp[i], dp[j] + (j - i));
//
// 当 dp[i] 这个位置维护好了 max,重点就是把它放在什么位置方便维护之后的 dp值
// 因为转移是通过前缀和关系,所以放置在线段树中也是对应排序后前缀和的位置

struct tree
{
	ll l, r;
	int mx1, mx2, mx3;
}tr[N << 2];
void pushup(tree& rt, tree& l, tree& r) { //三种状态的 max
	rt.mx1 = max(l.mx1, r.mx1);
	rt.mx2 = max(l.mx2, r.mx2);
	rt.mx3 = max(l.mx3, r.mx3);
}
void pushup(int u) {
	pushup(tr[u], tr[ul], tr[ur]);
}
void build(int u, ll l, ll r) {
	tr[u] = { l,r,-INF,-INF,-INF };//初始化极值
	if (l == r)return;
	else {
		ll mid = l + r >> 1;
		build(ul, l, mid), build(ur, mid + 1, r);
	}
}
void modify(int u, ll d, int v, int b) { 
	if (tr[u].l == d && tr[u].r == d) {
		tr[u].mx1 = max(tr[u].mx1, v - b);
		tr[u].mx2 = max(tr[u].mx2, v);
		tr[u].mx3 = max(tr[u].mx3, v + b);
	}
	else {
		ll mid = tr[u].l + tr[u].r >> 1;
		if (mid >= d)modify(ul, d, v, b);
		else modify(ur, d, v, b);
		pushup(u);
	}
}

//bug —— 传入边界的参数是longlong类型,定义int会runtime
//可以直接 #define int long long

tree query(int u, ll l, ll r) {
	if (tr[u].l >= l && tr[u].r <= r) {
		return tr[u];
	}
	else {
		ll mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid)return query(ul, l, r);
		else if (l > mid)return query(ur, l, r);
		else {
			tree uu, lu, ru;//两段区间对于三种状态都维护max
			lu = query(ul, l, mid);
			ru = query(ur, mid + 1, r);
			pushup(uu, lu, ru);
			return uu;
		}
	}
}

void solve() {
	cin >> n;
	a[0] = 0;//注意清空
	vec.clear();

	forr(i, 1, n) {
		cin >> a[i];
		a[i] += a[i - 1];
		vec.push_back(a[i]);
	}
	vec.push_back(0);//0是边界条件,即 dp[0]
	sort(all(vec));
	vec.erase(unique(all(vec)), vec.end());//离散前缀和

	m = vec.size();
	build(1, 1, m);

	forr(i, 0, n)a[i] = find(a[i]);//找到自己所属的线段树中的位置

	//初始化dp数组,在树中初始化边界
	dp[0] = 0;
	modify(1, a[0], 0, 0);

	forr(i, 1, n) {
		dp[i] = -INF;
		if (a[i] > 1) { //尝试从前缀和小于自己的部分转移过来
			tree u = query(1, 1, a[i] - 1);
			dp[i] = max(dp[i], u.mx1 + i);
		}
		if (a[i] < m) { //大于的部分
			tree u = query(1, a[i] + 1, m);
			dp[i] = max(dp[i], u.mx3 - i);
		}
		tree u = query(1, a[i], a[i]);//等于的部分
		dp[i] = max(dp[i], u.mx2);

		modify(1, a[i], dp[i], i);
	}

	cout << dp[n] << endl;
}

signed main() {
	cinios;
	int T = 1;
	cin >> T;
	forr(t, 1, T) {
		solve();
	}
	return 0;
}
/*
*/
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值