1. 程式人生 > 其它 >題解 P1721 [NOI2016]國王飲水記

題解 P1721 [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 2000\)

顯然水量 \(\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}+h_i-h_k}{i-k+1}\} \]

這個方程複雜度是 \(O(n^2kp)\) 的(高精度小數類的計算還有一個 \(p\) ),無法接受,考慮優化。

這個式子顯然是一個斜率優化的形式,把點 \((i-1,sumh_i-dp_i)\) 插入到一個下凸殼中,用三分查詢最優點,時間複雜度降為 \(O(nkp\log_3n)\) ,仍然還要優化。

發現決策具有單調性。證明:

  • 對於當前點 \((i-1,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;
}

完整程式碼