1. 程式人生 > 實用技巧 >插頭DP小結

插頭DP小結

插頭DP

插頭的定義:

對於兩個連通的塊,如圖所示,對於\((1,1)\)\((2,1)\),我們稱\((1,1)\)有一個下插頭,\((2,1)\)有一個上插頭

對於此圖則為一個閉環。

輪廓線的定義:

如圖所示,對於\(n\times m\)的網格,對於我們\(\begin{aligned}\mathcal{DP}\end{aligned}\)到的一個格子,我們都可以畫出如圖所示的一條線語文能力太差不會描述,這條畫出來的線就叫輪廓線,這條線的作用是分割已經\(\begin{aligned}\mathcal{DP}\end{aligned}\)過的區域和沒有\(\begin{aligned}\mathcal{DP}\end{aligned}\)

過的區域。輪廓線的具體用途為儲存已有的\(\begin{aligned}\mathcal{DP}\end{aligned}\)狀態。

\(\begin{aligned}\mathcal{DP}\end{aligned}\)的主體思路:那所以插頭\(\begin{aligned}\mathcal{DP}\end{aligned}\)到底要\(\begin{aligned}\mathcal{DP}\end{aligned}\)的是什麼?我們可以理解為目前\(\begin{aligned}\mathcal{DP}\end{aligned}\)到的格子的插頭的狀態,記錄目前插頭的狀態所對應的方案數。何為插頭狀態,就是插頭存在不存在,指向上下還是左右。對於不同的題我們可能在\(\begin{aligned}\mathcal{DP}\end{aligned}\)

過程中處理的細節不一樣,我們插頭代表的意義可能有所不同,但總體的\(\begin{aligned}\mathcal{DP}\end{aligned}\)過程中插頭的處理方法大致是一樣的。

對於此圖就是我們在\(\begin{aligned}\mathcal{DP}\end{aligned}\)過程中的例子,對於此格我們改變的只是在這個格子周圍的狀態,對於格子之外的位置的狀態並未改變,我們不用關注,跨過此格之後的輪廓線的J和J+1相對位置如圖所示,我們關注的就是這J和J+1位置的插頭有無,是否連通,方向

P5056 【模板】插頭dp

給出 \(n\times m\) 的方格,有些格子不能鋪線,其它格子必須鋪,形成一個閉合迴路。問有多少種鋪法?

我們設\(dp[i][j][s]\)\(\begin{aligned}\mathcal{DP}\end{aligned}\)到格子\(\begin{aligned}(i,j)\end{aligned}\),插頭狀態為s的方案數是多少,對於s我們使用四進位制數來儲存狀態

由輪廓線的上圖我們可以發現,對於我們目前\(\begin{aligned}\mathcal{DP}\end{aligned}\)到的格子,我們首先要考慮格子的左方和上方的連通性,再來考慮右方和下方的連通性

對於左方和上方的狀態,我們對應到輪廓線上是第J個位置和第J+1個位置,同時我們\(\begin{aligned}\mathcal{DP}\end{aligned}\)到的此格我們一定會有一進一出,對於小編號的位置我們設定相對位置為左,大編號的位置我們設定相對位置為右,儲存在s則是沒有為0,左為1,右為2

為什麼不用三進位制,你會閒的沒事來寫一個進位制優化嗎?能白嫖不香嗎

Type1:左閉上閉

考慮此格的下格和右格子是否是連通的,是連通的話我們就讓他開創新的插頭,並用我們的規定,讓其J位置的插頭方向為左,J+1位置插頭方向為右

Type2:左閉上開

對於上方我們的輪廓線位置是J+1,我們考慮此格的下方和右方的連通性,若下格連通,即移動後相對位置的J是連通的,我們只需將J+1位置的插頭抵消然後新開對於轉移後輪廓線J位置的插頭,若右方連通,即J+1位置連通,我們直接繼承即可,同時對於插頭的相對方向我們保持不變

Type3:左開上閉

對於上方我們的輪廓線位置是J,我們考慮此格的下方和右方的連通性,若右格連通,即移動後相對位置的J+1是連通的,我們只需將J位置的插頭抵消然後新開對於轉移後輪廓線J+1位置的插頭,若下方連通,即J位置連通,我們直接繼承即可,同時對於插頭的相對方向我們保持不變

Type4:左開右開

對於此情況,因為我們對於一個格子只會存在一進一出,所以我們必定從這兩個位置走,我們需要討論這兩個插頭在其各自連通路徑中的相對位置

1:左左上左

如圖,我們對於此情況,在連線之後,J+1路徑的右在連通之後變成了相對位置的左,J路徑的右邊的相對位置則相對位置不變,因此我們的操作就是消去上方左方的插頭,並改變相對位置

2:左右上左

如圖,我們對於此情況,在連線之後,J路徑與J+1路徑的相對左右沒有發生改變,對此我們直接繼承,並將兩個插頭消去

3:左左上右

對於此情況,這兩個插頭一定是屬於同一條相對路徑上的,因此在我們連線之後一定會形成一個閉合迴路,對於此題題意,我們必定會在最後來連線這個迴路,否則肯定會形成兩個以上的迴路不符合方案,遇到此情況就是我們要的答案

4:左右上右

如圖,我們對於此情況,在連線之後,J+1路徑的左在連通之後相對位置不變,J路徑的左在連通後變成了相對位置的右,因此我們的操作就是消去上方左方的插頭,並改變相對位置

然後我們會發現在轉移的時候我們只是在用\([i][j]\)\([i][j+1]\)的位置,因此我們可以考慮滾動陣列,然後我們考慮對於重複的狀態會增加複雜度,因此我們使用來優\(\begin{aligned}\mathcal{Hash}\end{aligned}\)化,這樣我們的空間大大縮小,時間複雜度為\(O(nm\times 4^{m+1})\)

#include<bits/stdc++.h>
    
#define LL long long
    
#define int long long

#define _ 0
    
using namespace std;
    
/*Grievous Lady*/
    
template <typename _n_> void mian(_n_ & _x_){
    _x_ = 0;int _m_ = 1;char buf_ = getchar();
    while(buf_ < '0' || buf_ > '9'){if(buf_ == '-')_m_ =- 1;buf_ = getchar();}
    do{_x_ = _x_ * 10 + buf_ - '0';buf_ = getchar();}while(buf_ >= '0' && buf_ <= '9');_x_ *= _m_;
}
    
unordered_map<int , int> dp[2];

int n , m , endx , endy , mp[101][101];

char c;

inline int oqs(int x , int y){
    return x << (y << 1);
}

inline int DP(){
    int tmp = (1 << ((m + 1) << 1)) - 1;
    int numnow = 0 , numnxt = 1;
    dp[numnow][0] = 1;
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= m;j ++){
            dp[numnxt].clear();
            for(auto k = dp[numnow].begin(); k != dp[numnow].end(); k ++){
                int S = k -> first , val = k -> second;
                int L = (S >> ((j - 1) << 1)) & 3 , R = (S >> (j << 1)) & 3;
                if(!mp[i][j]){ 
                    if(!L && !R) dp[numnxt][S] += val;
                    continue;
                }
                if(!L && !R){
                    if(mp[i][j + 1] && mp[i + 1][j]) dp[numnxt][S ^ oqs(1 , j - 1) ^ oqs(2 , j)] += val;
                }
                if(!L && R){
                    if(mp[i][j + 1]) dp[numnxt][S] += val;
                    if(mp[i + 1][j]) dp[numnxt][S ^ oqs(R , j) ^ oqs(R , j - 1)] += val;
                }
                if(L && !R){
                    if(mp[i][j + 1]) dp[numnxt][S ^ oqs(L , j - 1) ^ oqs(L , j)] += val;
                    if(mp[i + 1][j]) dp[numnxt][S] += val;
                }
                if(L == R){
                    if(L == 1 && R == 1){
                        int nowar = 0;
                        for(int p = j ; ; p ++){
                            int dou = (S >> (p << 1)) & 3;
                            if(dou == 1) nowar ++;
                            if(dou == 2) nowar --;
                            if(nowar == 0){
                                dp[numnxt][S ^ oqs(L , j - 1) ^ oqs(R , j) ^ oqs(2 , p) ^ oqs(1 , p)] += val;
                                break;
                            }
                        }
                    }
                    if(L == 2 && R == 2){
                        int nowar = 0;
                        for(int p = j - 1; ; p --){
                            int dou = (S >> (p << 1)) & 3;
                            if(dou == 2) nowar ++;
                            if(dou == 1) nowar --;
                            if(nowar == 0){
                                dp[numnxt][S ^ oqs(L , j - 1) ^ oqs(R , j) ^ oqs(1 , p) ^ oqs(2 , p)] += val;
                                break;
                            }
                        }
                    }
                }
                if(L == 2 && R == 1){
                    dp[numnxt][S ^ oqs(L , j - 1) ^ oqs(R , j)] += val;
                }
                if(L == 1 && R == 2 && i == endx && j == endy){ return val; }
            }
            swap(numnow , numnxt);
        }
        dp[numnxt].clear();
        for(auto k = dp[numnow].begin(); k != dp[numnow].end(); k ++){
            dp[numnxt][(k -> first << 2) & tmp] += k -> second;
        }
        swap(numnxt , numnow);
    }
    return 0;
}

inline int Ame_(){
    mian(n) , mian(m);
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= m;j ++){
            cin >> c;
            if(c == '*') mp[i][j] = 0;
            else if(c == '.') mp[i][j] = 1 , endx = i , endy = j;
        }
    }
    printf("%lld\n" , DP());
    return ~~(0^_^0);
}
    
int Ame__ = Ame_();
    
signed main(){;}

-----\(\begin{aligned}\mathcal{Ame}\end{aligned}\)__