题意:有k个空兵跳伞,第i个空兵有Li个位置可能会降落,降落在第j个位置xi,j的概率为为Pi,j,(保证(Σ(j=1,Li)Pi,j)=1)现在需要在坐标轴放置m个任务点,每个空兵降落后会往离自己的任务点走去,求每个空兵行走距离总和的期望值最小是多少。
(1<=k<=1000,1<=50<=m,m<=∑(i=1,k)Li<=1000)
首先,如果有多个空兵可能落在同一个点上,他们落在这一点的概率可以累加,所以可以使用计数法,而坐标范围是整个int,故可以开一个int到double的映射,完成去重和累计两个操作。
如果不考虑点的权值(即概率和),那么在区间[i,j]中放置一个点使[i,j]中的元素到这个点距离和最小,这个点必然是i+j>>1。而有了权值后,这并不成立,但仍能保证这个点一定是[i,j]中的某个元素,而且如果j往右靠,这个点绝不会往左靠。换句话说,随着j的右移,这个点的坐标保持递增或保持不变。
那接下来就是简单的三层循环了,方程:dp[i][j]=min{dp[i-1][k]+dis[k+1][j]},dp[i][j]是[1,j]中放i个点的最短总行走距离,dis[i][j]是区间[i,j]中放一个点的总行走距离。
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<map>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define memclear(a) memset(a,0,sizeof(a))
#define N 1000
#define M 50
using namespace std;
map<int,double>f;
map<int,double>::iterator it;
double dis[N+3][N+3];
double dp[M+3][N+3];
double prb[N],sprb[N];
int coor[N];
int n,m;
int main()
{
while(scanf("%d%d",&n,&m)&&(n||m))
{
f.clear();
int s,x,cnt=0;double p;
FOR(i,1,n)
{
scanf("%d",&s);
FOR(i,1,s)
{
scanf("%d%lf",&x,&p);
f[x]+=p;
}
}
sprb[0]=0;
for(it=f.begin();it!=f.end();it++)
{
cnt++;
coor[cnt]=(*it).first;
prb[cnt]=(*it).second;
sprb[cnt]=sprb[cnt-1]+prb[cnt];
}
FOR(i,1,cnt)
{
int pos=i;
FOR(j,i+1,cnt)
{ //pos为dis[i][j-1]取得最小值的任务点
double sum=dis[i][j-1]+prb[j]*(coor[j]-coor[pos]);
while(sprb[pos]-sprb[i-1]<sprb[j]-sprb[pos])
{
sum+=(coor[pos+1]-coor[pos])*((sprb[pos]-sprb[i-1])-(sprb[j]-sprb[pos]));
pos++;
}
dis[i][j]=sum;
}
}
FOR(i,1,cnt)dp[1][i]=dis[1][i];
FOR(i,2,m) //放i个任务点
FOR(j,i,cnt) //前j个坐标
{
dp[i][j]=2e9;
FOR(k,i-1,j-1) //前k个坐标放了i-1个任务点
dp[i][j]=min(dp[i][j],dp[i-1][k]+dis[k+1][j]);
}
printf("%.2lf\n",dp[m][cnt]);
}
return 0;
}