Problem
Solution
经过 瞎猜 思考可以得到以下性质:
- 所有高度小于首都的水箱肯定不会参与联通。
- 选择联通的水箱必然是从某一个水箱开始的连续的知道选到最高的水箱。因为如果中间有间隔,那么完全可以把前面的水箱舍弃掉最小的,然后换成间隔的较大的。
- 如果要联通多次水箱,必然是先与相对较低的水箱联通,再与相对较高的水箱联通。否则交换顺序更优,可以推推式子证明。
- k k k 最大取 n − 1 n-1 n−1 即可,就是每次仅联通一个更大的水箱。
由此就可以设状态了,设 f [ i ] [ j ] f[i][j] f[i][j] 表示第 i i i 次联通最后一个联通到 j j j 的最大平均高度。记 s [ i ] s[i] s[i] 表示前缀高度和,
f [ i ] [ j ] = max k = i − 1 j − 1 ( f [ i − 1 ] [ k ] + s [ j ] − s [ k ] j − k + 1 ) f[i][j]=\max_{k=i-1}^{j-1}\biggl(\frac {f[i-1][k]+s[j]-s[k]} {j-k+1}\biggr) f[i][j]=k=i−1maxj−1(j−k+1f[i−1][k]+s[j]−s[k])
这长得很像一个斜率表达式,把点看做 ( k − 1 , s [ k ] − f [ i − 1 ] [ k ] ) (k-1,s[k]-f[i-1][k]) (k−1,s[k]−f[i−1][k]) ,那么就是对 ( j , s [ j ] ) (j,s[j]) (j,s[j]) 找一个斜率最大的点转移。那么我们可以维护一个下凸壳,同时由于我们按高度进行排序了,也就是说点 j j j 到点 j + 1 j+1 j+1 的斜率也是单调递增的,那么在凸包上的切点也是单调的,因此用双端队列维护即可做到 O ( n k p ) O(nkp) O(nkp)了。
到这里你就能拿到85分了,然后可能就可以跑路了,因为后面用到的两个结论好难。。然而boshi表示用一个技巧就可以拿到100分了。就是我们可以先用double做一次转移,保存决策点,然后再用高精度小数库算答案即可。这样的时间复杂度为 O ( n 2 + n p ) O(n^2+np) O(n2+np)。
Code
由于高精度小数库太长了,所以我就截掉了,如果要编译的话就把高精度小数库贴在这份代码前就好了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=8010;
const double INF=1e15;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct vec{double x,y;}tmp,pt[maxn];
int n,k,p,tot,c,l,r,h[maxn],q[maxn];
ll s[maxn];
double f[2][maxn];
short g[maxn][maxn];
Decimal ans;
string str;
double slope(vec a,vec b){return (b.y-a.y)/(b.x-a.x);}
void input()
{
int x;
read(n);read(k);read(p);
read(h[1]);tot=1;
for(int i=2;i<=n;i++)
{
read(x);
if(x>h[1]) h[++tot]=x;
}
sort(h+1,h+tot+1);
getmin(k,tot-1);n=tot;
for(int i=2;i<=n;i++){h[i]-=h[1];s[i]=s[i-1]+h[i];}
}
Decimal dfs(int i,int j)
{
if(i==0) return Decimal(h[1]);
int pos=g[i][j];
Decimal res=dfs(i-1,pos),tmp;
tmp=res+(s[j]-s[pos]);
res=tmp/(j-pos+1);
return res;
}
int main()
{
input();
for(int i=1;i<=k;i++)
{
c^=1;q[l=r=1]=i;
pt[i].x=i-1;pt[i].y=s[i]-f[c^1][i];
for(int j=i+1;j<=n;j++)
{
tmp.x=j;tmp.y=s[j];
while(l+1<=r&&slope(pt[q[l]],tmp)<slope(pt[q[l+1]],tmp)) ++l;
f[c][j]=slope(pt[q[l]],tmp);
g[i][j]=q[l];
pt[j].x=j-1;pt[j].y=s[j]-f[c^1][j];
while(l+1<=r&&slope(pt[q[r-1]],pt[q[r]])>slope(pt[q[r]],pt[j])) --r;
q[++r]=j;
}
}
//printf("%.6lf\n",f[k&1][n]+h[1]);
for(int i=2;i<=n;i++){h[i]+=h[1];s[i]=s[i-1]+h[i];}
ans=dfs(k,n);
str=ans.to_string(p);
cout<<str;
return 0;
}