Codeforces Round #645 (Div. 2)——D题解
作为一名菜鸡,理所当然得没有A出来,这道题数据放小就一水题了,可惜数据这块卡的死死的。
本题最重要的一点就是你要推出来一个结论:答案的最后一天必定是月末,这样就可以枚举月末推出月初,前缀和计算天数即可。具体可以二分O(nlogn)也可以尺取O(n)
结论的证明:
这里参考Tutorial使用反证法,假设存在某一个最优解且其的最后一天不是月末,设该最优解的最后一天元素为x,则可以知道其下一天是x+1(因为它不在月末),因为其为最优解,故如果将该旅行计划向后延迟一天,则答案必不会比现在更优,而旅行计划向后延迟一天意味着减去最优解的左端点的元素加上x+1,因此可以知道左端点的元素>x+1,故左端点左边一天的元素>x,由此我们可以发现————将旅行计划向前提前一天,则可以加上一个>x的元素,减去一个x,发现答案优于最优解,矛盾。
具体可以结合图片:
完整的做法:
- 二分+前缀和
预处理好每月份天数的前缀和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<bits/stdc++.h>
#include<unordered_map>
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;
}
- 尺取
大体上思路其实和前者差不多,利用尺取的思想————l,r双指针,若当前天数和<k则r++,找到可能的末端点所在月份,然后再将l++,直到l处于起始端点的月份中,即(sum-a[l]=a[l+1]+…+a[r]<k且sum>=k)则计算利用与处理好的前缀和计算答案即可。
笔者没有自己写过,所以没代码,不过CF上面挺多的。