暂无链接
数学
【题目描述】
Shy 有一个长度为 n 的数组 a[i],让你把这个数组分成 k 份(每份包含至少一个元素,且每份中的元素连续)。假设其中一份长这个样子,b[1], b[2],…,b[m],那么他的价值是b[1]/b[1]+(b[1]+b[2])/b[2]+…+(b[1]+b[2]+…+b[m])/b[m],求在所有的合法的划分中,最小的价值和是多少。
【输入】
第一行两个整数 n,k 表示数组长度和分成的份数;
第二行为n个整数。
【输出】
输出一个数表示答案。(要求与标准答案的绝对或相对误差不差过 1/10000)。
【输入样例】
4 2
100 3 5 7
【输出样例】
5.7428571429
【提示】
【数据规模】
对于 30%的数据,1≤n≤1000;
对于 100%的数据,1≤n≤200000,1≤k≤min(50,n),1≤a[i]≤100000。
题解
考试的时候太懒了,写了个O(n3k)O(n3k)的暴力,A了pretestpretest连第一个档都没拿完就溜了。。。
不难看出,这是一道dpdp,我们用dp[i][j]dp[i][j]表示前ii个点切刀的最优方案,状态转移方程很好想,我们可以枚举断点:
上面的代价有个暴力的算法,设s+1s+1为整个块起点(即ss不包含在分出的块中),为终点,sum[i]sum[i]为每个点权值val[i]val[i]的前缀和:
博主想到这里就告辞了,O(n3k)O(n3k)居然有15分,出题人可以说是很友善了,但我们可以将上面的式子进一步变形:
那我们就可以预处理出sum[i]val[i]sum[i]val[i]的前缀和sdv[i]sdv[i],还有1val[i]1val[i]的前缀和rec[i]rec[i],便可以O(1)O(1)处理出贡献:
到此为止,我们就有30分了。。。
显然,长成这个样子,是可以斜率优化的,我们假设k<j<ik<j<i,且kk比优,为了叙述方便,我们省去第二维,用dp[i]dp[i]表示前ii个点的最优解:
dp[k]−sdv[k]+sum[k]rec[k]−(dp[j]−sdv[j]+sum[j]rec[j])sum[k]−sum[j]>rec[i]dp[k]−sdv[k]+sum[k]rec[k]−(dp[j]−sdv[j]+sum[j]rec[j])sum[k]−sum[j]>rec[i]
然后,rec[i]rec[i]单增,开始打板。。。
代码
#include<bits/stdc++.h>
#define db double
#define C(x) (dp[x][t]-sdv[x]+sum[x]*rec[x])
using namespace std;
const int M=2e5+5;
int que[M],val[M],n,p;
db rec[M],sdv[M],dp[M][55],sum[M];
void in()
{
scanf("%d%d",&n,&p);
for(int i=1;i<=n;++i)
{
scanf("%d",&val[i]);
sum[i]=val[i]+sum[i-1];
rec[i]=1.0/val[i]+rec[i-1];
sdv[i]=1.0*sum[i]/val[i]+sdv[i-1];
}
}
db calc(int le,int ri){return sdv[ri]-sdv[le]-sum[le]*(rec[ri]-rec[le]);}
db slop(int t,int k,int j){return 1.0*(C(k)-C(j))/(sum[k]-sum[j]);}
void ac()
{
for(int i=1;i<=n;++i)dp[i][1]=sdv[i];
int le,ri,k;
for(int i=2;i<=p;++i)
{
le=ri=0;
for(int j=1;j<=n;++j)
{
while(le<ri&&slop(i-1,que[le+1],que[le])<rec[j])le++;
k=que[le];
dp[j][i]=dp[k][i-1]+calc(k,j);
while(le<ri&&slop(i-1,que[ri],que[ri-1])>slop(i-1,j,que[ri]))ri--;
que[++ri]=j;
}
}
printf("%lf\n",dp[n][p]);
}
int main()
{
in();ac();
return 0;
}