1. 程式人生 > 其它 >[dp 記錄]agc024E Sequence Growing Hard

[dp 記錄]agc024E Sequence Growing Hard

題意:

給定一個序列,每次往其中插入 \([1,k]\) 間的一數,要求字典序遞增,求方案數。設插入 \(i\) 數的數列為 \(A_i\),存在一個 \(A_i\) 不同即兩插入方案不同,否則即一致。

\(n,k \leq 300\)

新增一個數,該數後面第一個與它數字的數字小於它本身。注意到連續相同數可以插入到任意位置,不妨欽定插到最後面,則每次轉移時 \(x_i > x_{i+1}\)

這樣仍然不好做。無論列舉填進去的數還是列舉填的位置都需要知道 \(n\) 個數的詳細資訊。

注意到操作序列能唯一確認方案,我們渴望找到什麼能與操作序列雙射。

看到操作序列,尤其轉移與大小相關,可以考慮上樹。

考慮怎樣計數操作,不會做。試想更簡單的問題:對於一個確定的序列,怎樣刻畫“插入”操作並對其計數?根據性質,一個數會插入在另一個數前。因此,可以考慮向位置靠後的數連邊。這樣,每個位置都有一個出邊,只有虛點沒有出邊。所以這是一棵樹,樹上每個節點的父親的權值都嚴格小於它。

—— https://www.luogu.com.cn/blog/Yansuan-HCl/solution-at-agc024-e

這樣還是不易於統計,再做一步轉化,把每個字元的位置用它被插入的時間表示。那麼我們需要做的即是對每次操作,選擇之前的一次操作表示這次插在之前插的位置前面。特別地,選擇 \(0\) 操作表示這次插在最後面。

——

https://www.luogu.com.cn/blog/0123456-3456789/solution-at3962

一次插入操作可以用它插入時在它後面的數刻畫。所以連邊,自然構成一棵樹。

定義一個節點的權值為二元組 \((val, id)\),每次操作新建點掛到它後面的點下面,\(id\) 為插入時間,\(val\) 為權值。則 \(\forall v\)\(u\) 兒子,\(val_v > val_u,id_v > id_u\)。容易驗證這樣的樹與操作序列雙射。這是漂亮的有傳遞性的偏序關係,無後效性的子結構,可以進行 dp 了。

記錄三維(大小也是應該關心的)還是多了些。發現每次轉移相當於加一棵子樹進來,欽定那是 \(id\)

最小或 \(val\) 最小就可以不用記錄其中一者轉移了。

\(id\) 是排列,關於排列的平移是唯一的,所以欽定 \(id\) 最小是好做的。

\(dp_{v,x}\) 表示根節點 \(val=v,siz=x\) 的樹的方案數。轉移:

\[dp_{v,x} = \sum_{i=v+1}^{k} \sum_{y=1}^{x-1} dp_{i,y}dp_{v,x-y}\binom{x-2}{y-1} \]

後面的組合數是因為兩邊的 \(val\) 的相對順序沒有限制,除了兩個根以外的相對順序都可以隨意選擇。

\(i\) 這一維字尾和滾掉即可做到 \(O(n^3)\)

#include <cstdio>
using namespace std;
const int M = 305;
int mod;
int add(int a, int b) {a += b; return a > mod ? a-mod : a;}
int mins(int a, int b) {a -= b; return a < 0 ? a+mod : a;}
void addn(int &x, int y) {x += y; if(x > mod) x -= mod;}
int n, dp[M][M], k, C[M][M], s[M][M];
int main(){
    scanf("%d %d %d", &n, &k, &mod);
    C[0][0] = 1;
    for(int i = 1; i <= n; i++) {
        C[i][0] = 1;
        for(int j = 1; j <= i; j++) 
            C[i][j] = add(C[i-1][j-1], C[i-1][j]);
        printf("\n");
    }
    for(int i = 0; i <= k; i++) dp[i][1] = 1, s[i][1] = k-i+1;
    for(int x = 2; x <= n+1; x++) {
        for(int v = k; v >= 0; v--) {
            for(int y = 1; y < x; y++) 
                addn(dp[v][x], 1ll * C[x-2][y-1] * s[v+1][y] % mod * dp[v][x-y] % mod);
            s[v][x] = add(s[v+1][x], dp[v][x]);
        }
    }
    printf("%d\n", dp[0][n+1]);
}