原题链接: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;
}