在 CF 本場比賽的討論區發現了解決這道題的一個比較新穎的思路,並且可以以 \(O(n^2)\) 的時間解決本題,來分享一下。
姑且稱這種 DP 方式為組件 DP。
在普通的 DP 方式中,我們常常針對位置進行 DP ,在組件 DP 中,我們按操作次序針對每個操作進行 DP。
概念
為了描述方便,記我們手動做出的選擇為 ⬛ ,記自動被選擇為 ⬜ 。
如果一些操作對應的位置連通,我們稱這些操作構成一組組件。例如,我們第一次操作手動選擇一號位置,第二次操作手動選擇二號位置,這兩個操作構成了一組組件( ⬛⬛ )。
而對於每次可以進行的操作,我們稱其為一個元件。本題中元件為: ⬛ ,表示手動選擇了一個位置。
轉移方程
設 \(f[i][j]\) 表示前 \(i\) 次操作后,存在 \(j\) 個組件的方案數。
我們有三種轉移方式:
1.添加一個組件
若當前有 \(j\) 組組件,則一共會存在 $ j+1$ 段間隔,在其中任意一段間隔中都可以以插入一個新元件 ⬛ 的方式添加一個組件。
2.在一個組件前/后添加一個元件
除了添加一個組件外,我們還可以在保證原有組件都聯通的基礎上增加一個原有組件的長度。在本題中有兩種添加方式:在一個組件緊挨着的位置上插入一個新元件 ⬛ ;或者在一個組件留一個空位,在這個空位旁插入一個新元件 ⬛ ,隨后這個空位會自動被 ⬜ 填充。
3.連接兩個組件
由於最后所有的操作必然要全部聯通,因此我們還需要考慮如何將兩個組件合並。
如果兩個組件間有兩個空位,我們選擇任意一個空位插入一個新元件 ⬛ ,隨后另一個空位會自動被 ⬜ 填充。
如果兩個組件間有三個空位,我們選擇中間的空位插入一個新元件 ⬛ ,隨后其余兩個空位會自動被 ⬜ 填充。
最后答案即為答案即為 \(f[n][1]\) ,時間復雜度 \(O(n^2)\)。
代碼
f[1][1]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
add(f[i+1][j+1],f[i][j]*(j+1));
add(f[i+1][j],f[i][j]*2*j);
add(f[i+2][j],f[i][j]*2*j);
if(j>1)add(f[i+2][j-1],f[i][j]*2*(j-1)),
add(f[i+3][j-1],f[i][j]*(j-1));
}
cout<<f[n][1]<<endl;