首先我们发现,一定不会有低于\(h_1\)的参与操作的过程。
然后考虑一个\(x\)与比它大的\(y<z\),则发现一定是先\((x,y)\),再\((\frac{x+y}{2},z)\)更好。
因为这样是\(\frac{4}{x+y}+\frac{z}{2}\),而一起做是\(\frac{x+y+z}{3}\),显然更优。
而每个节点一定只会和一号节点联通一次,因为如果联通过了那么肯定小于等于一号节点了。这里可以把\(k=\min(k,n)\)。
且每次操作一定包含一号节点,因为如果不包括一号节点相当于缩小了两个之间的差距,则会使其变小。
同时考虑,如果两次相邻操作之间范围有交,那么交换使得范围无交显然更优。并且如果两次之间有剩余的点,那么并入较小的一次会更优。
因此我们可以写出一个\(O(n^3p)\)的dp:设\(dp_{i,j}\)表示到了第\(i\)次,划分到从小到大第\(j\)个,\(1\)号的最大值。转移方程\(dp_{i,j}=\max\limits_{k=1}^{j-1}{\frac{dp_{i-1}{k}+Sum_j-Sum_k}{j-k+1}}\)。
容易发现这是一个斜率的形式,因此可以以每个点\((j-1,sum_j-dp_{i}{j})\)构建凸壳,然后每个询问点在凸壳上查找斜率最大点可以做到\(O(n^2\log np)\)。进一步的,因为\(\frac{Sum_i}{i}\)单调递增,因此和原点连线斜率递增。故可以决策单调性去掉三分,时间复杂度\(O(n^2p)\)。
观察到我们的时间消耗在dp过程中的高精度小数上,如果能每次求出最优转移点,然后最后再用高精度小数计算即可。过程中可以用一个double
计算转移,根据出题人题解,是有理有据地精度不会爆,因此将时间复杂度降至\(O(n(n+p))\)
code:
#include<bits/stdc++.h>
#define Gc() getchar()
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((k+1)*(x)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
#undef continue
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=8e3+5,M=1<<16|5,K=10000+5,mod=998244353,Mod=mod-1;const db eps=1e-5;
int n,m,k,p,x,Q[N],B[N],Bh,st[N],H,T;Decimal Ans;db f[N],g[N];short La[N][N];
db slope(int x,int y){return (Q[y]-g[y]-Q[x]+g[x])/(y-x);}
int main() {
freopen("1.in","r",stdin);freopen("1.out","w",stdout);
int i,j,h;scanf("%d%d%d",&n,&k,&p);for(i=1;i<=n;i++) scanf("%d",&Q[i]);sort(Q+2,Q+n+1);k=min(k,n-1);for(i=1;i<=n;i++) Q[i]+=Q[i-1];
for(i=1;i<=n;i++)f[i]=Q[1];for(i=1;i<=k;i++){Mc(g,f);Me(f,0);
st[H=T=0]=1;for(j=2;j<=n;j++){
//for(h=H;h<=T;h++) (g[st[h]]+Q[j]-Q[st[h]])/(j-st[h]+1)>f[j]&&(f[j]=(g[st[h]]+Q[j]-Q[st[h]])/(j-st[h]+1),La[i][j]=st[h]);
while(H^T&&(g[st[H]]+Q[j]-Q[st[H]])/(j-st[H]+1)<(g[st[H+1]]+Q[j]-Q[st[H+1]])/(j-st[H+1]+1)) H++;
La[i][j]=st[H];f[j]=(Q[j]-Q[st[H]]+g[st[H]])/(j-st[H]+1);f[j]<g[j]&&(f[j]=g[j],La[i][j]=j);
while(H^T&&slope(st[T-1],st[T])>slope(st[T],j)) T--;st[++T]=j;
//for(h=1;h<j;h++) (g[h]+Q[j]-Q[h])/(j-h+1)>f[j]&&(f[j]=(g[h]+Q[j]-Q[h])/(j-h+1),La[i][j]=h);
}
}x=n;for(i=k;i;i--) B[++Bh]=x,x=La[i][x];B[++Bh]=x;Ans=Q[1];for(i=k;i;i--) Ans=(Ans+Q[B[i]]-Q[B[i+1]])/(B[i]-B[i+1]+1);
cout<<Ans.to_string(2*p)<<'\n';
}
标签:frac,luogu,st,NOI2016,dp,using,节点,P1721,define
From: https://www.cnblogs.com/275307894a/p/16597470.html