蒟蒻的第一篇黑題題解,求過。

題目連結

題意描述

這道題用簡潔的話來說,就是:

給你 \(n\) 個數字,你可以讓取其中任意若干個數字,每次操作,都會使所有取的數字變為取的數字的平均數,並且你最多隻能進行 \(k\) 次操作,你要在這經過最多 \(k\) 次操作後使得給你的第一個數字變得最大。輸出保留 \(p\) 位的第一個數字的最後狀態。


貪心策略(非正解,有助後面做題)

我們暫且不研究這道題的正解是什麼,我們先看看怎麼樣貪心能夠使得第一個數字變得最大。

  • 首先,比第一個數字小的我們一定不需要,它們會使第一個數字變得更低

  • 第二,我們需要知道求平均數的順序,我們設三個水量, \(h1,h2,h3(h1 <h2<h3)\) 。現在我們通過比較不同的順序,看如何讓 \(h1\) 變得最大。

如圖所示,已經是所有的順序了:

  • \(h1,h2,h3\) 同時求平均數。

  • \(h1\) 先和 \(h3\) 求平均數,然後和 \(h2\) 求平均數。

  • \(h1\) 先和 \(h2\) 求平均數,然後和 \(h3\) 求平均數。

其實從圖中就可以看出來,第三種方案最優。


我們再用數學的方法來比較這三個方案的優劣性。

  • 第一種方案: \(h1=(h1+h2+h3)/3\)。

  • 第二種方案: \(h1=((h1+h3)/2+h2)/2=(h1+h3)/4+h2/2\)。

  • 第三種方案: \(h1=((h1+h2)/2+h3)/2=(h1+h2)/4+h3/2\)。

我們通過相減比較 \(h1\) 最後的大小:

  • 第一種方案減去第三種方案:
\[(h1+h2+h3)/3-(h1+h2)/4-h3/2
\]
\[\Longrightarrow (h1+h2)/3-(h1+h2)/4+h3/3-h3/2
\]
\[\Longrightarrow (4 \times h1+4 \times h2-3 \times h1-3 \times h2+4 \times h3-6 \times h3)/12
\]
\[\Longrightarrow (h1+h2-2 \times h3)/12
\]
\[\Longrightarrow ans<0
\]

第三種方案晉級。

  • 第二種方案減去第三種方案:
\[(h1+h3)/4+h2/2-(h1+h2)/4-h3/2
\]
\[\Longrightarrow h1/4-h1/4+h2/2-h2/4+h3/4-h3/2
\]
\[\Longrightarrow (h2-h3)/2-(h2-h3)/4
\]
\[\Longrightarrow ans<0
\]

第三種方案勝出。


總結策略

  • 只考慮比第一個數大的數。

  • 在一般情況下,一個一個操作(可能會超過 \(k\) 次,所以是在一般情況下)。

  • 在一個一個操作時,從小到大操作。


斜率優化dp(正解)

由於可進行的操作次數是一定的,所以我們最後要解決的就是該在那一段區間進行操作。

首先將大於第一個數的數記錄下來,排序並求出字首和。

然後設計dp轉移方程:

\(sum_i\) 為前 \(i\) 個數的字首和(操作是賦值為平均數,並不會改變總數值,所以 \(sum_i\) 一直適用) 。

\(f_{i,j}\) 表示前面 \(i\) 個數操作了 \(j\) 次時第一個數的最大值。

得轉移方程:

\[f_{i,j}=\max((f_{p,j-1}+sum_i-sum_{p-1})/(i-p+1))
\]

code

for(int j=1;j<=k;j++)
for(int i=1;i<=n;i++)
for(int p=1;p<i;p++)
f[i][j]=max(f[i][j],(f[p][j-1]+sum[i]-sum[p-1])/(i-p+1));

但是這樣的話,轉移的時間複雜度為 \(O(n^2kp)\) ,(\(n\) 是數字個數, \(k\) 是操作次數,p為保留的精度)。

所以光是dp是不行的,我們要考慮優化dp。


我們觀察這個狀態轉移方程:

\[(f_{p,j-1}+sum_i-sum_{p-1})/(i-p+1)
\]
\[\Longrightarrow (sum_i-(sum_{p-1}+f_{p,j-1}))/(i-(p-1))
\]

由於 \(i\) 和 \(j\) 是迴圈中給出的,所以我們將 \(i\) 和 \(j\) 提出:

\[\Longrightarrow (sum_{p-1}-f_p)/(p-1)
\]

經過前後對比,我們發現,設當前點為 \((i,sum_i)\) ,轉移點為 \((p-1,sum_{p-1}-f_p)\) ,其實就是斜率公式。

用斜率優化dp,還有很重要的條件是要有單調性。如圖,這道題目的單調性也十分的明顯,由於 \(1\le h_i \le 10^5\) ,也就意味著字首和是單調上升的。




圖畫的不好,諒解。

那麼我們要維護的就是圖中的這一個凸包。

接下來我們考慮彈出佇列的數會不會對後面的操作有影響:

設當前位置為 \(i\) ,對於 \(i\) ,我們設 \(k_2\) 優於 \(k_1\) , 在這個情況下,我們可以得出:

\[(f_{k_1,j-1}+sum_i-sum_{k_1-1})/(i-k_1+1)<(f_{k_2,j-1}+sum_i-sum_{k_2-1})/(i-k_2+1)
\]

如果將 \(i+1\) 帶入,結果為:

\[(f_{k_1,j-1}+sum_{i+1}-sum_{k_1-1})/(i+1-k_1+1)<(f_{k_2,j-1}+sum_{i+1}-sum_{k_2-1})/(i+1-k_2+1)
\]

比較兩個式子,我們會發現,在 \(i+1\) 的情況下,如同 \(i\) 的情況相同, 原本優於 \(k_1\) 的 \(k_2\) 依然會比 \(k_2\) 更優,所以彈掉 \(k_1\) 並不會影響後面的操作。


code

for(int j=1;j<=k;j++)
{
int head=tail=1;
q[tail]=(node){1,sum[1]};
for(int i=2;i<=n;i++)
{
node x=(node){i,sum[i]};
while(head<tail&&slope(x,q[head])<slope(x,q[head+1]))head++;
f[i][j]=(f[q[head]][j-1]+sum[i]-sum[q[head]-1])/(i-q[head]+1);
while(head<tail&&slope(q[tail],q[tail-1])>slope(q[tail],i)) tail--;
q[++tail]=i;
}
}

此時我們已經將時間複雜度調小至 \(O(nkp)\) 。

\(n\le 8000\ \ k\le10^9\ \ p\le 3000\) 。

顯然,我們還是不能過這道題目。

於是我們再次重審題目,看一下還有什麼條件我們並沒有用到。

Two thousands years later......

哦,我發,發現就怪了。

於是我參考了巨佬ljh2000的部落格,終於發現了:\(h_i\) 互不相同。 那麼那麼就意味著......

好吧......

於是我又一次參考了巨佬ljh2000的部落格,裡面有兩條十分重要的性質:

  • 每一次操作的區間長度一定不比上一次操作的區間長度長!

  • 在所有水量高度互不相同的情況下,長度大於1的區間僅有 \(O(\log{ \frac{nh}{H}})\) 個,其中 \(H=\min(h_i-h_{i-1})\) 。

首先我們證明第一個,十分簡單。我們的目標是將 \(h_1\) 變得最大,而我們在一開始的時候對 \(h_i\) 進行了排序,所以越往後面, \(h_i\) 越大,越大的餅自然越要少與人分享,所以我們就會得出第一個結論。

至於第二個結論,我們只好根據眾多大佬的指引,來到這裡,深造一番。

Three thousands years later......

很好,沒有任何的作用,看來只能等待某位大佬的指點,或者等本蒟蒻再深造幾年再回答這個問題吧。

蒟蒻拙見,大佬勿噴。