题解 [NOI2016]国王饮水记

题解 洛谷 P1721 [NOI2016]国王饮水记

题目链接

题意描述:

\(n\) 个水量为 \(h_i\) 的储水罐,每次操作可以选任意多个储水罐 \(a_1\sim a_x\) ,使这些储水罐的水量全部变为 \(\frac{\sum\limits_{i=1}^x h_{a_i}}{x}\) ,最多进行 \(k\) 次,问 \(1\) 号储水罐水量最多有多少,答案精度误差不大于 \(10^{-p}\)
\(n\leq 8000,k\leq 10^9,p\leq 3000\)

显然水量 \(\leq h_1\) 的储水罐没有任何贡献,所以先按 \(h_i\) 从小到大排序,再以 \(1\) 为起点计算。

考虑怎样合并储水罐答案最大:

  • 方案一:直接合并所有要合并的储水罐,不妨设合并 \(3\) 个,答案为 \(\frac{h_1+h_2+h_3}{3}\)
  • 方案二:分多次合并储水罐,不妨设每次合并 \(2\) 个,答案为 \(\frac{\frac{h_1+h_2}{2}+h_3}{2}\)

这里由于我们已经排序,所以 \(h_1<h_2<h_3\)

通分:

\[\begin{aligned}\frac{h_1+h_2+h_3}{3}&=4\times(h_1+h_2+h_3)\\&=4\times h_1+4\times h_2+4\times h_3\end{aligned} \]

\[\begin{aligned}\frac{\frac{h_1+h_2}{2}+h_3}{2}&=6\times(\frac{h_1+h_2}{2}+h_3)\\&=3\times(h_1+h_2)+6\times h_3\\&=3\times h_1+3\times h_2+6\times h_3\end{aligned} \]

两式相减:

\[\begin{aligned}\frac{h_1+h_2+h_3}{3}-\frac{\frac{h_1+h_2}{2}+h_3}{2}&=(4\times h_1+4\times h_2+4\times h_3)-(3\times h_1+3\times h_2+6\times h_3)\\&=h_1+h_2-2\times h_3\end{aligned} \]

\[\because h_1\leq h_2\leq h_3 \]

\[\therefore h_1+h_2\leq 2\times h_3 \]

\[\therefore \frac{h_1+h_2+h_3}{3}\leq \frac{\frac{h_1+h_2}{2}+h_3}{2} \]

\(x\geq 3\) 时可以归纳证。

由上可知分开合并不会比一起合并更劣,所以我们尽量地分开合并,同时也告诉我们, \(k\leq 10^9\) 的数据范围是吓人的

\(dp_{i,j}\) 表示合并前 \(i\) 个储水罐,花费 \(j\) 次操作的答案,用 \(sumh_i\) 表示 \(h_i\) 的前缀和,可以得到转移方程:

\[dp_{i,j}=\max_{1\leq k<i}\{\frac{dp_{k,j-1}+sumh_i-sumh_k}{i-k+1}\} \]

这个方程复杂度是 \(O(n^2kp)\) 的(高精度小数类的计算还有一个 \(p\) ),无法接受,考虑优化。

这个式子显然是一个斜率优化的形式,把点 \((i-1,sumh_i-dp_i)\) 插入到一个下凸壳中,用三分查找最优点,时间复杂度降为 \(O(nkp\log_3n)\) ,仍然还要优化。

发现决策具有单调性。证明:

  • 对于当前点 \((i,sumh_i)\) ,有一个最优的转移点 \((j-1,sumh_j-dp_j)\) 由于合并后平均水量一定增加,所以如果对于一个次优的转移点 \((k-1,sumh_k-dp_k)\) ,有:

\[i>j>k \]

\[sumh_i>sumh_j-dp_j>sumh_k-dp_k \]

  • \(\because 1\leq h_i\leq 10^5\)\(h_i\) 互不相同
  • \(\therefore sumh_i\geq i-1\)
  • 根据下凸壳性质,得到 \(sumh_j-dp_j\geq j-1,sumh_k-dp_k\geq k-1\)
  • \(\therefore\) 下一个决策点至少为 \((i+1,sumh_i+i)\)

证毕。

此时可以不需要三分,复杂度降至 \(O(nkp)\) ,但对于 \(8000\times 8000\times 2000\) 的范围还是无法通过。

发现 \(n\)\(p\) 是搞不掉了,只能在 \(k\) 上面动刀子,这时就要想一想合并的 \(h\) 有什么性质了。由于 \(h_i\) 互不相同,所以排序后 \(h\) 一定是一个上升序列,对于上升序列中的一段区间的平均值,我们发现后面比前面取的更少会更优,即决策的 \(\Delta i\) 是单调不增的。证明比较简单

\[y-x+1>z-y+1 \]

\[p-x+1\leq z-p+1 \]

\[x\leq p<y\leq z \]

\[\frac{\frac{\sum\limits_{i=x}^{y}h_i}{y-x+1}+\sum\limits_{i=y+1}^{z}h_i}{z-x+1}>\frac{\frac{\sum\limits_{i=x}^{p}h_i}{p-x+1}+\sum\limits_{i=p+1}^{z}h_i}{z-x+1} \]

  • 则有

\[2\times y>x+z \]

\[2\times p\leq x+z \]

\[\frac{\sum\limits_{i=x}^{y}h_i+\sum\limits_{i=y+1}^{z}h_i\times (y-x+1)}{y-x+1}>\frac{\sum\limits_{i=x}^{p}h_i+\sum\limits_{i=p+1}^{z}h_i\times (p-x+1)}{p-x+1} \]

  • 通分

\[\sum\limits_{i=x}^{y}h_i\times (p-x+1)+\sum\limits_{i=y+1}^{z}h_i\times (y-x+1)\times (p-x+1)>\sum\limits_{i=x}^{p}h_i\times (y-x+1)+\sum\limits_{i=p+1}^{z}h_i\times (y-x+1)\times (p-x+1) \]

  • 发现

\[\sum\limits_{i=x}^{p}h_i\times (p-x+1)<\sum\limits_{i=x}^{p}h_i\times (y-x+1) \]

\[\sum\limits_{i=p+1}^{y-1}h_i\times (p-x+1)<\sum\limits_{i=p+1}^{y-1}h_i\times (y-x+1)\times (p-x+1) \]

\[\sum\limits_{i=y}^{z}h_i\times (y-x+1)\times (p-x+1)=\sum\limits_{i=y}^{z}h_i\times (y-x+1)\times (p-x+1) \]

与假设矛盾,故对于

\[y-x+1>z-y+1 \]

\[p-x+1\leq z-p+1 \]

\[\frac{\frac{\sum\limits_{i=x}^{y}h_i}{y-x+1}+\sum\limits_{i=y+1}^{z}h_i}{z-x+1}\leq \frac{\frac{\sum\limits_{i=x}^{p}h_i}{p-x+1}+\sum\limits_{i=p+1}^{z}h_i}{z-p+1} \]

所以合并区间长度单调不增。

有了这个性质,可以发现合并次数不会超过 \(\log \frac{n\times h}{\min(h_i-h_{i-1})}\) 次,约为 \(14\) 次,这时问题就迎刃而解了。

点击查看代码
Decimal ans;
int n,K,p,cnt;
int H[N],Sum[N];
double dp[N][25];
int pos[N][25];
int q[N],hd,tl;
vector<pair<double,double> >t;
double Slope(int i,int j){return(double)(t[j].se-t[i].se)/(t[j].fi-t[i].fi);}
Decimal print(int cur,int stp){
	if(stp==0)return H[1];
	return(print(pos[cur][stp],stp-1)+Sum[cur]-Sum[pos[cur][stp]])/(cur-pos[cur][stp]+1);
}
signed main(){
	n=read();K=read();p=read();H[++cnt]=read();
	for(int i=2;i<=n;i++){
		int x=read();
		if(x>H[1])H[++cnt]=x;
	}
	n=cnt;
	sort(H+1,H+n+1);
	for(int i=1;i<=n;i++)Sum[i]=Sum[i-1]+H[i];
	for(int i=1;i<=n;i++)dp[i][0]=H[1];
	K=min(K,n);
	int lim=min(K,14);
	for(cnt=1;cnt<=lim;cnt++){
		hd=1;tl=0;
		q[++tl]=1;
		t.clear();t.pb(mp(0,0));
		for(int i=1;i<=n;i++)t.pb(mp(i-1,Sum[i]-dp[i][cnt-1]));
		for(int i=2;i<=n;i++){
			t.pb(mp(i,Sum[i]));
			while(hd<tl&&Slope(n+1,q[hd])<Slope(n+1,q[hd+1]))hd++;
			t.pop_back();
			pos[i][cnt]=q[hd];
			dp[i][cnt]=1.*(dp[q[hd]][cnt-1]+Sum[i]-Sum[q[hd]])/(i-q[hd]+1);
			while(hd<tl&&Slope(q[tl-1],q[tl])>Slope(q[tl],i))tl--;
			q[++tl]=i;
		}
	}
	cnt=n-K+lim;
	int id=0;
	for(int i=0;i<=lim;i++)
		if(dp[cnt][i]>dp[cnt][id])id=i;
	ans=print(cnt,id);
	for(int i=cnt+1;i<=n;i++)ans=(ans+H[i])/2;
	cout<<ans.to_string(p<<1)<<endl;
	return 0;
}

完整代码

一个坑点:题面中给的高精度小数类的精度是 \(10^{-2100}\)

posted @ 2021-11-09 20:13  pidan007  阅读(174)  评论(0编辑  收藏  举报