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值)
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;
}
/*
*/