Codeforces Round 924 (Div. 2) D. Lonely Mountain Dungeons【枚举+贪心+数学】

原题链接:https://codeforces.com/problemset/problem/1928/D

题意翻译

有 n 类人,第 i 类人有 ci​ 个。你需要 (k−1)*x 的代价把他们分成k≥1 组。在一种分组下,每有两个同类的人被分到不同的组,会产生 b 的贡献。求最大收益。

n≤2×10^5,1≤ci​≤2×10^5,b≤10^6,x≤10^9。

所有测试数据的 c1​+⋯+cn​ 之和 ≤2×10^5。

输入输出描述:

输入输出样例
输入
5
3 1 0
1 2 3
3 5 10
2 5 3
4 3 3
3 2 1 2
4 1 0
4 1 4 2
4 1 10
4 1 4 2
输出 
4
40
9
13
0

解题思路:

首先可以想到的就是可以直接枚举分为多少组,先将c按照从小到大排序,那么我们最多可以分为cn组,如果多于cn组只会让减去的和x有关的值更多,加上的和b有关的值不变,让总兵力变小,所以排序之后我们最多只会枚举到分为cn组,想到枚举这不难,但是确定了分为多少组之后,对于每一个ci应该怎么分配才能让总兵力最大呢,这个时候可以想到有点类似绝对值不等式那个意思,当平均分配的时候可以让分在不同组中的对数最多,就是让总兵力最大,下面来证明一下。

首先这种情况的总对数是固定的,那么这种情况的贡献就是总对数减去每一组内的对数,当分配不均匀时,我们把剩余的ci分配至ci值较小的位置减少的对数肯定比分配在ci值较大的位置减少的对数少,而总对数是固定的,所以应该平均分配才能保证总兵力最大。

上面已经知道了分配方式,直接计算比较麻烦,那么怎么推出一个比较方便的计算公式呢,这又是一个难点,我们不妨设C(x)表示x这个组中的俩俩成对对数,根据上面我们推出的计算公式,贡献=总对数-每一组内的对数。

假设某一个ci,总对数为C(ci),我们枚举分为i组,对于ci<=i的组可以直接计算,但是对于ci>i的组不好计算,那么首先每一组内平均分配每一个组拥有ci/i个元素,然后还会剩下ci%i个元素,多出来的ci%i个元素平均分配到ci%i组,每一组一个。

那么贡献=总对数-每一组的对数=C(ci)-C(ci/i)*i-(ci/i)*(ci%i)。

上面的计算公式中C(ci)表示总对数,C(ci/i)*i表示平均分配给每一组的对数,(ci/i)*(ci%i)表示多出来的ci%i个元素分配在ci%i个组,每个组内原本有(ci/i)个元素形成的对数,所以这一部分也是要减去的。

时间复杂度:这个复杂度看起来像O(V^2),v表示所有ci之和,但是实际上由于ci之和最大值2e5(1<=i<=n),所以每次枚举总个数不超过2e5/i,那么时间为O(V/1+V/2+...+V/V)=O(V*log(V)),其中V表示ci之和,最终时间复杂度为O(nlog(n)+Vlog(V))。

空间复杂度:O(n)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N=1e5+10;

int T,n,b,x;
int c[N];

LL C(int x)
{
    return 1ll*x*(x-1)/2;
}
void solve()
{
    cin>>n>>b>>x;
    for(int i=1;i<=n;i++)cin>>c[i];
    sort(c+1,c+1+n);
    LL ans=0,sum=0;
    int pos=1;
    for(int i=1;i<=c[n];i++)
    {
        while(pos<=n && c[pos]<=i){
            sum+=C(c[pos]);  //c[pos]<=i可以直接计算
            pos++;
        }
        LL v=sum;
        for(int j=pos;j<=n;j++)  //c[j]>i不可以直接计算,需要通过推出公式计算
        {
            v+=C(c[j])-C(c[j]/i)*i-c[j]/i*(c[j]%i);
        }
        ans=max(ans,v*b-1ll*(i-1)*x);
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}
  • 23
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值