新的学生宿舍开放了,它由M栋建筑物构成,标号为1到M。开始时,学生宿舍都是空的,很快有N个学生搬进去了。刚好每天搬进去一个。
每次有新同学搬进宿舍,那栋建筑将会举行一个大型的party。party的噪声和该建筑物里的学生的数量相等。宿舍管理员不喜欢噪声,所以他们会不定期的清空某栋建筑物。清空的方法就是把该栋建筑物的学生全部赶到另外的学生宿舍(这M栋宿舍以外的地方)。但是管理员最多只能清空K次。求这n天噪音之和的最小值。
【输入格式】
第一行三个整数n,m,k。
接下来有n行,每行一个整数bi(1<=bi<=m),表示第i天有一个学生搬进了bi栋宿舍。
【输出格式】
一个整数,表示这n天噪音之和的最小值。
【输入样例】
5 1 2
1
1
1
1
1
【输出样例】
7
【样例解释】
样例1解释:在第一天和第三天之后执行清空操作。这样每天的噪声为1,1,2,1,2,所以噪声之和为7.
【数据范围】
1<=n<=1000000,1<=m<=100,1<=k<=500
这次考试坠最坑的一个题了吧,考场上最先想的用f(i,j,k)来描述前i天,前j个寝室清理k次的最小噪音和,然后,就卡在转移了,而且面对n这么大的规模我也只能一脸懵逼,显然这样的状态是不行的,然后想省去i这一维,却卡在转移上了,然后想搜素,结果每一天的选择都特别多,而且天数又是辣么的大,又歇菜了,最后胡乱写了一个程序证明我都是做完的的交了上去,结果当然是GG了。
后来考完了HB说要二分答案+贪心,.smg???二分答案猜一个标准limit,然后说只要有一个寝室在这一天大于了这个标准就该清理,如果清理完了总的次数小于了k,则在左边继续猜limit,否则在右边猜limit,用坠吼最后猜到的limit,模拟一次把噪声值算出来就是答案。大家都觉得不科学。
但是后来CXW那坨(原谅我不会量词)和YKL等人研究了一中午,说这个题应该是动态规划解的,因为Mr.he错误的认为了所有寝室都必须达到了limit才需要清理,但是事实上,如果把有些寝室的清空标准降低,或许可以得到更优的解。设f(i,j)表示前i个宿舍清空j次的最小噪声和。
考虑第i个寝室,可以清空0次,问题转化为求f(i-1,j),f(i,j)=f(i-1,j)+c[i][0] //这里c[i][j]表示将第i号宿舍清空j次所能得到的最小噪声和
......1次f(i-1,j-1)+c[i][1].....
即f(i,j)=min{f(i-1,j-k)+c[i][k] | 0<=k<=j };
关于c[i][j]的计算,其实可以预先通过输入知道一个寝室在n天中总共被x人住过,然后就是把x人分成j+1组使得噪声值的和最小,根据噪声值的算法容易想见:
c[i][j]=1+...+x/(j+1)+x%(j+1)*(x/(j+1)+1);
代码实现:
#include<cstdio>
#include<cstdlib>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#define maxn 1005
using namespace std;
typedef long long LL;
const LL inf = 200000000000000ll;
LL f[maxn][maxn],c[maxn][maxn];
/*
f(i,j)表示前i个寝室,清空j次的最小噪音和
f(i,j)=min{ f(i-1,j-x)+c[i][x] | 0<=x<=j} //其中c[i][x]表示对第i栋建筑清空x次的最小噪音
f(0,j)=0;
Ans=f(m,k);
*/
int n,m,k;
int a[maxn];//a[i]表示i号寝室住过的总人数
void ready()//计算c[i][j]
{
for(int i=1;i<=m;i++)
for(int j=0;j<=k;j++)
{
LL p=a[i]/(j+1),q=a[i]%(j+1),s=0;
for(int r=1;r<=p;r++)
s+=r;
c[i][j]=s*(j+1)+q*(p+1);
}
}
void dp()
{
ready();
for(int i=1;i<=m;i++)
for(int j=0;j<=k;j++)
{
LL t=inf,tt=inf;
for(int x=0;x<=j;x++)
{
t=min(t,f[i-1][j-x]+c[i][x]);
tt=min(t,tt);
}
f[i][j]=tt;
}
cout<<f[m][k]<<'\n';
}
void _read(int &x)
{
x=0;
bool flag=1;
char t=getchar();
while(t<'0' || t>'9')
{
if(t=='-')flag=0;
t=getchar();
}
while(t>='0' && t<='9')
{
x=x*10+t-'0';
t=getchar();
}
if(!flag) x=-x;
}
int main()
{
//freopen("dorm.in","r",stdin);
//freopen("dorm.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++)
{
int x;
_read(x);
a[x]++;
}
dp();
return 0;
}