Description:
某日,竞赛班的学生来到了一家糖果店。
店里卖着M袋糖果,第i袋糖果里装有i颗糖,价格为i¥。
有N个学生对这些糖果产生了兴趣,于是迅速站成一排,且将他们编号为1到N。其中第i个学生带着a[i]¥。每一轮,他们按顺序买糖果(每一轮每个人只会买一袋)。由于体内的竞争之魂与超乎常人的不服输精神,当前学生买的这袋糖果一定会比上一个人多(当然,第一次可以随便买)。如果第N个人买了糖果,那么就到第1个人开始下一轮。
然而,钱和糖果都有限,总是要停下来的。可以在任意时刻停止购买糖果,但是最后一次必须是第N个人购买。
现在他们想知道,最终所有人购买糖果数之和最大可以是多少。(当然可以一袋都不买~)
1≤T≤5 2≤N≤500,000 N≤M≤5,000,000 1≤a[i]≤ (m*(m+1)/2)
题解:
贪心。
一种很优的贪心:
1.可以先枚举买多少轮,当前有k轮,一个比较容易想到的方法就是先按1、2、3、4……这样子放,然后a是还有剩余的,于是倒着拔高,直到不能不能拔高为止。时间复杂度:O((m / n)^2 * n)
2.假设要拔高i在j层的数,如果它的后面已经全部拔高到顶,即m,m-1,m-2这样子,那么当前就可以拔高(m - b[i])(前提是a够),b[i]为i在第k层的原来的大小,我们发现这是恒定的。那么假设要把整个第j层给拔高到顶,实际上花费和第j+1层是一样的。于是就可以直接算后面有多少层能够直接拔高到顶,假设后u层能够拔高到顶,对于倒数第u+1层,你可以想象再把它们整体拔高到中间,这时候一定有a为0,于是最后还能把倒数第u+2层拔高一点,a就用到极限了。
时间复杂度:O(T * (m / n) *n) =O(T*m)
Code:
#include<cstdio>
#define ll long long
#define fo(i, x, y) for(ll i = x; i <= y; i ++)
#define fd(i, x, y) for(ll i = x; i >= y; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;
ll T, n, m, ans, a[500005], c[500005];
void read(ll &x)
{
char c = ' ';
for (c=getchar();c<'0' || c>'9';c=getchar());
x = 0;
for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
}
int main() {
freopen("compete.in", "r", stdin);
freopen("compete.out", "w", stdout);
for(scanf("%lld", &T); T; T --) {
scanf("%lld %lld", &n, &m);
fo(i, 1, n) read(a[i]), c[i] = a[i];
ll s = 0;
fo(hang, 1, m / n) {
if(hang * n > m) break;
int ab = 0;
fo(i, 1, n) {
a[i] = c[i] - (2 * i + (hang - 1) * n) * hang / 2;
if(a[i] < 0) {
ab = 1; break;
}
}
if(ab) break;
ll x = hang * n; ans = (1 + hang * n) * hang * n / 2;
ll min = hang;
fo(i, 1, n) min = min(min, a[i] / (m - x));
fo(i, 1, n) ans += min * (m - x), a[i] -= min * (m - x);
if(min != hang) {
ll mi = m - x;
fd(i, n, 1)
mi = min(mi, a[i]), a[i] -= mi, ans += mi;
if(min != hang - 1) {
fd(i, n, 1)
mi = min(mi, a[i]), a[i] -= mi, ans += mi;
}
}
s = max(s, ans);
}
printf("%lld\n", s);
}
}