學習筆記 插頭DP

2022-04-29 05:45:11 字數 4764 閱讀 4358

基於連通性的狀壓dp問題。

一般是給你乙個網格,有一些連通性的限制。

鏈結題意:網格圖,去掉一些點,求哈密頓迴路方案數。

一般按格遞推(從上到下,從左到右)。

每個格仔要從四個方向中選兩個作出邊。

我們只需要記錄紅色的輪廓線的狀態,是否有邊伸出這個線(稱之為插頭), 還要記錄伸出來的邊的連通性。

記錄連通性的方法:

最小表示法

括號表示法(適用範圍較小,效率一般更高):出邊是兩兩配對的(如果沒有回來就有終點了);並且出邊不可能交叉(因為如果有交叉就經過重複點了)。咱們用三進製表示,\((\) 對應 \(1\),\()\) 對應 \(2\),沒有邊對應 \(0\)。看似最壞狀態是 \(3 ^ \) 的,但要保證括號配對,所以大概有效狀態會很少,因此不要以最壞複雜度來分析插頭 dp,大概可以打個表式一下極限資料。

設 \(f_\) 為考慮到 \(i, j\) 當前輪廓線狀態是 \(s\) 的方案數。

分類討論,設上狀態為 \(y\),左狀態為 \(x\):

如果 \((i, j)\) 是障礙物。需要 \(x = y = 0\)。狀態不變。

否則,若 \(x = y = 0\),則 \(x \gets 1, y \gets 1\)

\(x = 0\),\(y \not= 0\),列舉一下 \(y\) 從下面和右邊出去兩種情況。

\(x \not= 0\),\(y = 0\),同 3,向下或向右走。

\(x = y = 1\),必然要連起來,把右邊配對的兩個插頭較左的 \(2\) 變成 \(1\)。(即 \(y\) 的配對變成 \(1\))

\(x = y = 2\),同 5 ,把 \(x\) 對應的配對插頭變成 \(2\)。

\(x = 2, y = 1\),把兩個插頭去掉賦 \(0\)。

\(x = 1, y = 2\),將整個迴路封死,只能在整個格仔的最後乙個格仔去封。(只會發生在最後乙個格仔)。

想要把**變得美一點、短一點,大概是做到了吧...

這裡沒有用雜湊表,把 \(42000\) 個狀態先 dfs 出來,存到陣列裡,再預處理一下每個狀態每對括號的匹配。

這樣每次轉移可以 \(o(1)\),但是由於狀態對應到編號我用的二分,所以複雜度是 \(o(n^2 s \log s)\),其中 \(s\) 是總狀態數大概是 \(s \le 42000\)。

#include #include #include #include using namespace std;

typedef long long ll;

const int n = 15, s = 42000;

int n, m, d[s], tot, w, c[n], p[s][n], now[n], top, s[n], l;

int ex, ey;

ll ans, f[2][s], h[s];

bool st[n][n];

char g[n][n];

int inline query(int x)

int inline ask(int x, int i)

int inline get(int i, int t)

void inline add(int a, int b)

void inline work(int x)

d[++tot] = x;

for (int i = 0; i < l; i++) p[tot][i] = now[i];

}void dfs(int u, int s, int cnt)

dfs(u - 1, s, cnt);

if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);

if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);

}int main()

dfs(l - 1, 0, 0);

f[0][1] = 1;

for (int i = 1; i <= n; i++) else if (!x && !y) add(u, d[u] + get(j - 1, 1) + get(j, 2));

else if (!x && y) add(u, d[u]), add(u, d[u] + get(j - 1, y) - get(j, y));

else if (x && !y) add(u, d[u]), add(u, d[u] - get(j - 1, x) + get(j, x));

else if (x == 1 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y) - get(p[u][j], 1));

else if (x == 2 && y == 2) add(u, d[u] - get(j - 1, x) - get(j, y) + get(p[u][j - 1], 1));

else if (x == 2 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y));

else if (x == 1 && y == 2 && i == ex && j == ey && d[u] - get(j - 1, 1) - get(j, 2) == 0) ans += f[!w][u];

}} }

printf("%lld\n", ans);

return 0;

}

題意:有權網格圖,求迴路最大權值。

狀態同上面,可以用括號序列維護(兩兩配對)。

轉移稍有不同,每個封口都可以給 \(\text\) 貢獻,另外上題的分類 1 可以考慮不選這個格仔。

#include #include #include using namespace std;

const int n = 105, m = 7, s = 42000, inf = 0xcfcfcfcf;

int n, m, a[n][m], d[s], tot, w, c[m], p[s][m], now[m], top, s[m], l;

int ans = -2e9, f[2][s], h[s];

int inline query(int x)

int inline ask(int x, int i)

int inline get(int i, int t)

void inline add(int a, int b, int v)

void inline work(int x)

d[++tot] = x;

for (int i = 0; i < l; i++) p[tot][i] = now[i];

}void dfs(int u, int s, int cnt)

dfs(u - 1, s, cnt);

if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);

if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);

}int main()

} }printf("%d\n", ans);

return 0;

}

題意:用 l 鋪滿網格圖(有障礙物)的方案數。

不需要存連通性,要存每個插頭有沒有拐彎,三進製狀態就可以。

選行列短的當列做,這樣狀態總數就是 \(\le 3 ^ \) 的。

寫了一次三進製,貌似蠻好寫的,預處理出來 \(3\) 的冪次(也就是權),這樣模擬位運算都是 \(o(1)\) 的。

#include #include #include using namespace std;

const int n = 105, m = 12, s = 180000, p = 20110520;

int n, m, ex, ey, ans, pow[m], f[2][s], w, h[s];

char g[n][n];

bool st[n][n];

int inline ask(int x, int i)

int inline get(int i, int t)

void inline add(int a, int b)

void inline out(int x)

}int main()

for (int i = 1; i <= n; i++)

for (int j = 1; j <= m; j++)

if (g[i][j] == '_') ex = i, ey = j, st[i][j] = true;

f[0][0] = 1;

for (int i = 1; i <= n; i++) else if (!x && !y) add(u, u + get(j - 1, 2) + get(j, 2)), add(u, u + get(j - 1, 1)), add(u, u + get(j, 1));

else if (x == 0 && y == 1) add(u, u + get(j, 1)), add(u, u - get(j, 1) + get(j - 1, 1));

else if (x == 0 && y == 2) else if (x == 1 && y == 0) add(u, u + get(j - 1, 1)), add(u, u - get(j - 1, 1) + get(j, 1));

else if (x == 1 && y == 1) else if (x == 2 && y == 0)

}} }

printf("%d\n", ans);

return 0;

}

插頭dp學習筆記

由於插頭dp很難懂於是又來記筆記了 插頭dp可以用來解決一些連通性狀壓問題。具體流程是分格仔處理,然後可以根據需要進行滾動,狀壓一下輪廓線狀態,常用4進製 之類的。拿luogu例題做例子 給出n m的方格,有些格仔不能鋪線,其它格仔必須鋪,形成乙個閉合迴路。問有多少種鋪法?n,m 2 n,m 12 ...

插頭DP學習

隊內沒人會插頭dp,感覺這個不會不行。所以我還是默默去學了一下,學了一天,感覺會了一點。對於每一行,一共有j 1個插頭,如果是多迴路類的題目,比較簡單,可以用1表示有插頭,0表示沒有插頭,這樣就可以愉快轉移了,對於當前出來的位置 i,j 與它有關的插頭有j 1和j 那麼我們可以列舉狀態經行轉移。對於...

插頭DP 入門

強烈推薦 hdu 1693 eat the trees 多迴路的不用判聯通狀態,二進位制即可,轉移情況2 2種。時間o n m 2 n 空間o n 2 n 插頭dp include include const int maxm 13 const int maxn 1 12 typedef long ...