codeforces Round #645 (Div. 2)D题解

Codeforces Round #645 (Div. 2)——D题解
作为一名菜鸡,理所当然得没有A出来,这道题数据放小就一水题了,可惜数据这块卡的死死的。
本题最重要的一点就是你要推出来一个结论:答案的最后一天必定是月末,这样就可以枚举月末推出月初,前缀和计算天数即可。具体可以二分O(nlogn)也可以尺取O(n)
结论的证明:
这里参考Tutorial使用反证法,假设存在某一个最优解且其的最后一天不是月末,设该最优解的最后一天元素为x,则可以知道其下一天是x+1(因为它不在月末),因为其为最优解,故如果将该旅行计划向后延迟一天,则答案必不会比现在更优,而旅行计划向后延迟一天意味着减去最优解的左端点的元素加上x+1,因此可以知道左端点的元素>x+1,故左端点左边一天的元素>x,由此我们可以发现————将旅行计划向前提前一天,则可以加上一个>x的元素,减去一个x,发现答案优于最优解,矛盾。
具体可以结合图片:
codeforces Round #645 (Div. 2)D题解_第1张图片
完整的做法:

  1. 二分+前缀和
    预处理好每月份天数的前缀和x以及每月份的拥抱数前缀和数组y。
    枚举每个月份的最后一天,即对于每一个x[i]>=k的,利用upper_bound查找x中第一个>x[i]-k的下标j(此时i代表末端所在的月份,j代表首段所在月份)利用前缀和y数组计算出两个端点之间的拥抱数(y[i]-y[j-1])注意此时是多计算一部分的拥抱数的,因为j月份只是包含端点,所以再利用x数组计算出j月份中多了多少天(x[i]-x[j-1]-k),使答案减去该天数的拥抱数即(t*(t+1)/2)即可
    代码:
#include
#include
using namespace std;
#define ll long long 
ll x[400005];
ll y[400005];
ll z[400005];
int main()
{
    ll n, m;
    cin >> n >> m;
    int i;
    for (i = 0; i < n; i++)
    {
        scanf("%d", &x[i]);
        x[i + n] = x[i];
    }
    n = n * 2;
    for (i = 0; i < n; i++)
    {
        if (i)
        {
            y[i] = x[i] + y[i - 1];
            z[i] = x[i] * (x[i] + 1) / 2 + z[i - 1];
        }
        else
        {
            y[i] = x[i];
            z[i] = x[i] * (x[i] + 1);
        }
    }
    ll ma = 0;
    for (i = 0; i < n; i++)
    {
        if (y[i] >= m)
        {
            int pos = upper_bound(y, y + n, y[i] - m) - y;
            ll ans = z[i] - z[pos];
            ll days = y[i] - (pos==0?0:y[pos-1]);
            ll cha = days - m;
            
            ans = ans - (cha) * (cha + 1) / 2 + (x[pos]) * (x[pos] + 1) / 2;
            //cout << cha << endl;
            ma = max(ma, ans);
        }
    }
    cout << ma << endl;
}
  1. 尺取
    大体上思路其实和前者差不多,利用尺取的思想————l,r双指针,若当前天数和=k)则计算利用与处理好的前缀和计算答案即可。
    笔者没有自己写过,所以没代码,不过CF上面挺多的。

你可能感兴趣的:(codeforces Round #645 (Div. 2)D题解)