Competing Souls

Competing Souls ⁡ \operatorname{Competing\ Souls} Competing Souls

题目链接: j z o j   5245 jzoj\ 5245 jzoj 5245

题目

某日,竞赛班的学生来到了一家糖果店。

店里卖着 M M M 袋糖果,第i袋糖果里装有i颗糖,价格为 i i i ¥。

N N N 个学生对这些糖果产生了兴趣,于是迅速站成一排,且将他们编号为 1 1 1 N N N 。其中第 i i i 个学生带着 a [ i ] a[i] a[i] ¥。每一轮,他们按顺序买糖果(每一轮每个人只会买一袋)。由于体内的竞争之魂与超乎常人的不服输精神,当前学生买的这袋糖果一定会比上一个人多(当然,第一次可以随便买)。如果第 N N N 个人买了糖果,那么就到第 1 1 1 个人开始下一轮。

然而,钱和糖果都有限,总是要停下来的。可以在任意时刻停止购买糖果,但是最后一次必须是第 N N N 个人购买。

现在他们想知道,最终所有人购买糖果数之和最大可以是多少。(当然可以一袋都不买~)

输入

第一行一个整数 T T T ,表示有 T T T 组询问。

对于每组询问:第一行两个整数 N , M N,M N,M ,意义见题目。接下来 N N N 行,每行一个正整数 a [ i ] a[i] a[i] ,表示第 i i i 个人带了 a [ i ] a[i] a[i] ¥

输出

对于每组询问单独一行输出一个整数,表示答案。

样例输入

2
2 5
5
10
3 8
8
16
13

样例输出

13
32

样例解释

对于第一组数据:
第一轮,两人分别买了第 1 , 3 1,3 1,3
第二轮,两人分别买了第 4 , 5 4,5 4,5
最终答案 1 + 3 + 4 + 5 = 13 1+3+4+5=13 1+3+4+5=13

第二组数据:
第一轮,三人买的: 2 , 4 , 5 2,4,5 2,4,5
第二轮,三人买的: 6 , 7 , 8 6,7,8 6,7,8
最终答案 2 + 4 + 5 + 6 + 7 + 8 = 32 2+4+5+6+7+8=32 2+4+5+6+7+8=32

数据范围

30 % 30\% 30% M ≤ 20 M≤20 M20
另有 10 % 10\% 10% N = 2 N=2 N=2
100 % 100\% 100% 1 ≤ T ≤ 5   ;   2 ≤ N ≤ 500 , 000   ;   N ≤ M ≤ 5 , 000 , 000   ;   1 ≤ a [ i ] ≤ ( m ∗ ( m + 1 ) / 2 ) 1≤T≤5\ ;\ 2≤N≤500,000\ ;\ N≤M≤5,000,000\ ;\ 1≤a[i]≤ (m*(m+1)/2) 1T5 ; 2N500,000 ; NM5,000,000 ; 1a[i](m(m+1)/2)

思路

这道题用贪心。

我们枚举买的轮数,然后先填最小的情况,假设有 n n n 个人,就像这样:

  1. 第一轮选 1 1 1 2 2 2 3 3 3 n n n
  2. 第二轮选 n + 1 n+1 n+1 n + 2 n+2 n+2 n + 3 n+3 n+3 2 ∗ n 2*n 2n
  3. 第三轮选 2 ∗ n + 1 2*n+1 2n+1 2 ∗ n + 2 2*n+2 2n+2 2 ∗ n + 3 2*n+3 2n+3 3 ∗ n 3*n 3n
    ……
    (以此类推)

然后就从最后一轮的最后一个数开始,尽量加到最大。在加的过程中,要注意几个点:

  1. 加了之后的糖果带里面的糖果不能超过 m m m
  2. 加了之后这个人一定要有钱买。
  3. 加了之后的糖果带里面的糖果不能比这一轮后面糖果带里面的糖果多。

这样就可以了。

代码

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

ll T, n, m, a[500001], now, minn, tim, sheng[500001], ans;

int main() {
//	freopen("compete.in", "r", stdin);
//	freopen("compete.out", "w", stdout);
	
	scanf("%lld", &T);
	
	for (ll TT = 1; TT <= T; TT++) {
		ans = 0;
		
		scanf("%lld %lld", &n, &m);
		for (ll i = 1; i <= n; i++)
			scanf("%lld", &a[i]);
		
		for (ll i = 1; i <= m / n; i++) {
			now = (1 + (i * n)) * (i * n) >> 1;//(头+尾)*个数/2求出总共多少
			minn = (ll)1 << 60;
			for (ll j = 1; j <= n; j++) {
				a[j] -= n * (i - 1) + j;
				minn = min(minn, a[j]);
			}
			if (minn < 0) break;
			
			tim = min(i, minn / (m - i * n));
			minn = tim * (m - i * n);
			now += tim * (m - i * n) * n;
			for (ll j = 1; j <= n; j++)
				sheng[j] = a[j] - minn;//求出剩下还有多少钱
			minn = m - i * n;
			for (ll j = i - tim; j >= i - tim - 1 && j >= 1; j--)
				for (ll k = n; k >= 1; k--) {
					minn = min(minn, sheng[k]);//找到最多可以加多少
					sheng[k] -= minn;
					now += minn;//加上能再加的
					if (!minn) break;
				}
			
			ans = max(ans, now);//求出最多的那一个
		}
		
		printf("%lld\n", ans);//输出
	}
	
//	fclose(stdin);
//	fclose(stdout);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值