Codeforces Round #407 (Div. 2) C 数学 + DP

题目链接

思路:
首先我们可以将每相邻两项的差的绝对值算出来,然后按顺序形成一个新的数列:
如样例

1 4 2 3 1   -->    3 2 1 2

记新数组为 a
观察公式,选取某一起点元素u,终点元素v,则:

f(l,r) = a[u] - a[u+1] + a[u+2] - a[u+3] ... a[v]

即正负号依此出现,呈交错性。

因为起点u和终点v可以任意选,则原问题转化为:
给你两个数组,求任意连续区间和的最大值

对于样例,即为:

求 a1: 3 -2 1 -2 和 a2: -3 2 -1 2 中任意连续区间和的最大值。

然后就变成了一个经典的问题。
朴素的枚举起点终点 或者 枚举区间长度 都是O(n^2) 此题果断超时没商量。

此时有两种写法进行选择:
一 .DP: 对于某一个元素 i,其作为终点时连续区间和的最大值dp[i]满足 :
d p [ i ] = m a x ( 0 , d p [ i − 1 ] ) + a [ i ] dp[i] = max(0,dp[i-1]) + a[i] dp[i]=max(0,dp[i1])+a[i]

二.处理前缀和 + 动态维护最小前缀和:

因为对于某一个元素 i,其作为终点时连续区间和的最大值dp[i]满足:
d p [ i ] = s u m [ i ] − m i n ( s u m [ j ] ) ( 0 = &lt; j &lt; i &lt; = n ) dp[i] = sum[i] - min(sum[j]) (0=&lt; j&lt;i&lt;=n) dp[i]=sum[i]min(sum[j])(0=<j<i<=n)

其中sum为前缀和,sum[j]为 i 之前最小的前缀和。

for(int i=1 ;i<=n ;i++){    // sum[0] = 0
	sum[i] = sum[i-1] + a[i];
}
for(int i=1 ;i<=n ;i++{
	ans = max(ans,sum[i] - Min);
	Min = min(Min,sum[i]);
}

然后本蒟蒻选的第一种DP写法:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int A = 1e5 + 100;
int a[A];
ll v_1[A],v_2[A];
ll dp_1[A],dp_2[A];

int main(){
    int n;
    scanf("%d",&n);

    for(int i=1 ;i<=n ;i++){
        scanf("%d",&a[i]);
    }

    int now = 1;
    for(int i=1 ;i<n ;i++){
        v_1[i] = now * abs(a[i+1] - a[i]);
        v_2[i] = -now * abs(a[i+1] - a[i]);
        now = -now;
    }

    ll ans = -1;
    dp_1[0] = dp_2[0] = 0;
    for(int i=1 ;i<n ;i++){
       dp_1[i] = max((ll)0,dp_1[i-1]) + v_1[i];
       dp_2[i] = max((ll)0,dp_2[i-1]) + v_2[i];
       ans = max(ans,dp_1[i]);
       ans = max(ans,dp_2[i]);
    }

    printf("%I64d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值