洛谷P1721/bzoj4654/loj2087/uoj223 [NOI2016]国王饮水记 斜率优化

题目分析

性质

  1. 所有积水高度小于等于1号点的点可以直接丢掉。
    所以,将留下来的水的高度都改成其原本的高度-1号点高度,最后答案再加上1号点的高度。
  2. 假如被要求进行两次合并,有两杯水 h 1 &lt; h 2 h _ 1&lt;h _ 2 h1<h2,则一定先合并低的,再合并高的。
    证明:先合并低的: 1 2 ( 1 2 h 1 + h 2 ) = 1 4 h 1 + 1 2 h 2 \frac{1}{2}(\frac{1}{2} h _ 1+ h _ 2)=\frac{1}{4} h _ 1+\frac{1}{2} h _ 2 21(21h1+h2)=41h1+21h2,先合并高的: 1 2 h 1 + 1 4 h 2 \frac{1}{2} h _ 1 + \frac{1}{4} h _ 2 21h1+41h2
  3. 若有一部分水合并了,一部分没有,那么被合并的水一定是最高的几杯。

将所有留下来的水从低到高排序,设 h i h _ i hi表示第 i i i杯水的高度, s i s _ i si表示前 i i i杯水的高度前缀和。

合并操作一定是堆在最后面的一段一段的区间,从前往后合并。

f ( i , j ) f(i,j) f(i,j)表示前 j j j杯水合并了 i i i次。

f ( i , j ) = m a x ( f ( i − 1 , k ) + s j − s k j − k + 1 ) f(i,j)=max(\frac{f(i-1,k)+s _ j-s _ k}{j-k+1}) f(i,j)=max(jk+1f(i1,k)+sjsk)

观察这个式子,发现是点 ( k − 1 , s k − f ( i − 1 , k ) ) (k-1,s _ k - f(i-1,k)) (k1,skf(i1,k))和点 ( j , s j ) (j, s _ j) (j,sj)构成的直线的斜率。

用单调队列维护点 ( k − 1 , s k − f ( i − 1 , k ) ) (k-1,s _ k - f(i-1,k)) (k1,skf(i1,k))构成的下凸壳。因为点 ( j , s j ) (j, s _ j) (j,sj)的y坐标随x坐标递增,所以找最优决策时,将队首一段不会构成最优决策的点的弹出,队首就是最优决策。

代码

那个600多行的高精度小数类就直接删掉了……只要记得将开头的const int PREC=后面的数字改为3000以上即可。

将每一步的决策用short记录下来,然后用高精度小数类再算一次答案。

using namespace std;
#define RI register int
typedef long double db;
typedef long long LL;
const int N=8005;
Decimal ans;
int orzyyb,n,K,P,kpos;db kans;
int h[N],qid[N];LL s[N];short las[N][N];db f[2][N];
struct point{db x,y;}que[N];
point operator - (point A,point B) {return (point){A.x-B.x,A.y-B.y};}
db operator * (point A,point B) {return A.x*B.y-B.x*A.y;}

Decimal getans(int x,int y) {
	if(x==0) return 0;
	return (getans(x-1,las[x][y])+s[y]-s[las[x][y]])/(y-las[x][y]+1);
}

int main() {
	scanf("%d%d%d",&orzyyb,&K,&P);
	scanf("%d",&h[0]);
	for(RI i=1;i<orzyyb;++i) {
		int x;scanf("%d",&x);
		if(x>h[0]) h[++n]=x-h[0];
	}
	sort(h+1,h+1+n);
	for(RI i=1;i<=n;++i) s[i]=s[i-1]+h[i];
	if(K>n) K=n;
	for(RI i=1,t=1;i<=K;++i,t^=1) {
		int he=1,ta=1;
		que[1]=(point){(db)i-2,(db)s[i-1]-f[t^1][i-1]},qid[1]=i-1;
		for(RI j=i;j<=n;++j) {
			point P=(point){(db)j,(db)s[j]};
			while(he<ta&&(P-que[he+1])*(P-que[he])<0) ++he;
			las[i][j]=qid[he],f[t][j]=(db)(P.y-que[he].y)/(db)(P.x-que[he].x);
			P=(point){(db)j-1,(db)s[j]-f[t^1][j]};
			while(he<ta&&(que[ta]-que[ta-1])*(P-que[ta-1])<0) --ta;
			++ta,que[ta]=P,qid[ta]=j;
		}
		if(f[t][n]>=kans) kans=f[t][n],kpos=i;
	}
	ans=getans(kpos,n)+h[0];
	cout<<ans.to_string(P<<1)<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值