【NOIP2017模拟8.8A组】Competing Souls

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);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值