title
【问题描述】
FJ有M个牛棚,编号1至M,刚开始所有牛棚都是空的。
FJ有N头牛,编号1至N,这N头牛按照编号从小到大依次排队走进牛棚,每一天只有一头奶牛走进牛棚。第i头奶牛选择走进第p[i]个牛棚。
由于奶牛是群体动物,所以每当一头奶牛x进入牛棚y之后,牛棚y里的所有奶牛们都会喊一声“欢迎欢迎,热烈欢迎”,由于声音很大,所以产生噪音,产生噪音的大小等于该牛棚里所有奶牛(包括刚进去的奶牛x在内)的数量。
FJ很讨厌噪音,所以FJ决定最多可以使用K次“清空”操作,每次“清空”操作就是选择一个牛棚,把该牛棚里所有奶牛都清理出去,那些奶牛永远消失。“清空”操作只能在噪音产生后执行。
现在的问题是:FJ应该选择如何执行“清空”操作,才能使得所有奶牛进入牛棚后所产生的噪音总和最小?
【输入】
第一行,N、M、K。
接下来有N行,每行一个整数,第i行是p[i]。
【输出】
一个整数,最小的噪音总和。
【输入输出样例1】
noise.in
5 1 2
1
1
1
1
1
noise.out
7
样例解释1:
第1头奶牛进入牛棚且产生噪音后,“清空”牛棚。第3头奶牛进入牛棚且产生噪音后,再次“清空”牛棚。5头奶牛产生的噪音依次是:1,1,2,1,2。如果没有“清空”操作,5头奶牛产生的噪音依次是:1,2,3,4,5。
【输入输出样例2】
noise.in
11 2 3
1
2
1
2
1
2
1
2
1
2
1
noise.out
18
样例解释2:
第3头奶牛进入牛棚1且产生噪音后,“清空”牛棚1。第7头奶牛进入牛棚1且产生噪音后,“清空”牛棚1。第6头奶牛进入牛棚2且产生噪音后,“清空”牛棚2。
【数据范围】
对于40%的数据,M=1。
对于60%的数据,1<=N <= 1000。
对于80%的数据,1<=N <= 50000。
对于100%数据, 1<=N <= 1000000, 1<=M<=100, 1<=K<= 500。
-
:
样例中给定的 的情况,
分析一下可以发现我们要尽可能的把一个牛棚均匀分成 段,
那么我们只需要在 等分点上清空就好了。
通过暴力可以得到 分. -
:
考虑贪心,我们将清空次数尽量分给牛多的牛棚,可以骗到 分. -
:是 (惊不惊喜,意不意外)。
的情况可以告诉我们第 个牛棚清空 次都是有最优解的,那么我们可以求出 ,表示第 个牛棚清空 次的最小噪音值。 这样我们可以枚举第几个牛棚清空几次。然后可以发现这就是背包问题,再开一个数组 表示 个牛棚中清空 次的最小噪音值,就可以解决了。- 求出 是一个难点。假设我们要把第 个牛棚清空 次,那么实际上我们就是把第 个牛棚分成了 段,我们的目的是把每一段的牛的数量尽可能的靠近 ,但是有可能不能平均分,这个时候就有一些段要分 头牛,具体有多少段呢? ,平均分的段数自然也就出来了。
- 这道题的关键就是各个牛棚之间不互相影响,那么对于每一个局部部分我们可以先算出局部最优解,最后合并成全局最优解,是一种通用的技巧。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10,maxm=110,maxk=510;
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
inline ll C(ll x)
{
return x*(x+1)>>1;
}
ll num[maxn],f[maxm][maxk],g[maxm][maxk];
int main()
{
freopen("noise.in","r",stdin);
freopen("noise.out","w",stdout);
int n,m,k;
read(n);read(m);read(k);
for (int i=1; i<=n; ++i)
{
int x;read(x);
++num[x];
}
for (int i=1; i<=m; ++i)
for (int j=1; j<=k; ++j)
{
f[i][0]=C(num[i]);
for (int l=1; l<=j; ++l)
{
ll a=num[i]%(j+1),b=j+1-a;
f[i][j]=b*C(num[i]/(j+1))+a*C(num[i]/(j+1)+1);
}
}
for (int i=1; i<=m; ++i)
for (int j=0; j<=k; ++j)
{
g[i][j]=g[i-1][j]+f[i][0];
for (int l=1; l<=j; ++l)
g[i][j]=min(g[i][j],g[i-1][j-l]+f[i][l]);
}
printf("%lld\n",g[m][k]);
return 0;
}