cf1358D,思维题

cf1358D,1900的题
题意:一年有n个月,然后这n个月的天数各不相同,分别为d[0],d[1]…d[n],你可在其中选择连续的len天,且如果你在一个月的第j天去的话能获得j个积分,问连续的len天能获得最多的积分是多少,且这连续len天可以不是同一年。
思路:这个第一个说实话我想了挺久的,别人的都是首先就有~~,我tm真是个lj~~ ,那就是取得最优的连续len天的最后一天必定是一个月的最后一天。这个解释有正着证明的,也有反着证明的。
正着证明:
d[0],d[1]…d[n]
1,2…d[0]…12…d[1]…d[n]
设这个数组为a
所以我们能得到我们要求的是a[i-x]+…+a[i],即前n项和S[i]-S[i-x],然后变一下形式∑(a[i]-a[i-len]), a[i-len]是有一段长度为len为0

红色的线是a[i-x],黑色的线是a[i]
红色的线是a[i-x],黑色的线是a[i]
最后得到的一个结论就是到某个月的最后一天,然后再往后一天,就要加上一个负数,减去一个数,所以可得取得最大积分的时候,右区间的端点一定是某个月的最后一天。

反着证明的话就容易多了,但是。。。谁一开始就知道这个结论的啊/吐血.jpg
假设这个长度为len的左端点的值为L,右端点的值为R,这里的是前面数组a中的值,所以在[L, R]中分情况讨论。
先假设R不是某个月的最后一天,前原本所能得到的积分为ans
当R=L时,显然原本的区间不是最优的区间
当R>L时,整个区间往后移动一天,新区间的积分为(ans+R+1-L)> ans
当R<L时,分两种情况
当R+1=L时,对这个区间一直右移,直到有个左右端点有一个达到某个月的最后一天,因为如果没有到达最后一天的话,之前的积分都是ans不变,左边-L右边+R+1,ans不变,
如果是L先到达端点,向右移动一天,ans-L+R+1=ans不变
如果是R先到达端点,向右移动一天,ans-L+1<ans.可以理解为当达到右端点达到最后一天时是最优的区间
如果是R和L同时到达,向右移动一天,ans-L+1<ans同理
当R+1<L时,向前移动一天,新区间的积分为ans+L-1-R>ans,不是最优区间

得到最有区间的右端点一定是某个月最后一天的结论后,之后的就挺简单的了,对每个月的最后一天进行穷举,然后二分之后一个max就行,具体实现可参考代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for(int i = a; i < n; i++)
#define per(i, a, n) for(int i = n-1; i >= a; i--)
#define INF 1ll<<60
const int maxn = 4e6+10;
ll d[maxn];
ll s[maxn], ss[maxn];
int main(){
    ll n, len;
    scanf("%lld%lld", &n, &len);
    rep(i,0,n){
        scanf("%lld", &d[i]);
        d[i+n] = d[i];
    }
    n = n*2;
    s[0] = d[0], ss[0] = (d[0]+1)*d[0]/2;
    rep(i,1,n){
        s[i] = d[i]+s[i-1];
        ss[i] = ss[i-1]+(d[i]+1)*d[i]/2;
    }
    ll ans = 0;
    rep(i,0,n){
        if(s[i]>=len){
            int x = lower_bound(s, s+n, s[i]-len)-s;
            ll tmp = ss[i] - ss[x];
            if(s[x]!=s[i]-len){
                tmp += (s[x]-s[i]+len)*(d[x]*2-s[x]+s[i]-len+1)/2;
            }
            ans = max(ans, tmp);
        }
    }
    printf("%lld", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值