题目大意:给你n个数,和一个w,每个数的概率是把这个数的值/n个数总值,让你把这些n个数分成w组,每个组的值为这个组加上前面所有组的数字个数之和*这个组的概率之和,要使所有的这些组的和最小。
思路:因为每个组的概率之和前面要乘系数,而且是越早选越小,那么我们把所有数字按照大小,从大到小排序,但由于一个组加进去数字如果过多,它前面的系数也会变大,使前面那些大数去乘一个比较大的系数,这里就需要DP一下。设d[ i ][ j ] 表示前i个区域分成j个组能获得的最小值,状态转移方程为:d[ i ][ j ] = min(d[ i - k - 1 ][ j - 1 ] + sum( i-k ~ i ),k为i与前面几个数一起,0<=k<i )。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x0fffffff ;
const int MAXN = 111 ;
int a[MAXN],sum[MAXN];
int d[MAXN][MAXN];
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,w;
scanf("%d%d",&n,&w);
sum[0] = 0;
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1,cmp);
for(int i = 1;i<=n;i++)
sum[i] = sum[i-1]+a[i];
for(int i =0;i<=n;i++)
for(int j= 0;j<=max(n,w);j++)
d[i][j] = INF;
d[0][0] = 0;
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=min(i,w);j++)
{
for(int k = 0;k<i;k++)
{
d[i][j] = min(d[i][j],d[i-k-1][j-1] + (sum[i]-sum[i-k-1])*i);
}
//printf("d[%d][%d] = %d\n",i,j,d[i][j]);
}
}
int ans = INF;
for(int i =1;i<=min(n,w);i++)
ans = min(ans,d[n][i]);
printf("%.4f\n",(double)ans/sum[n]);
}
return 0;
}