1. 程式人生 > 實用技巧 >棋盤式(基於連通性)DP模型總結

棋盤式(基於連通性)DP模型總結

導語

  狀壓DP分兩大類,一類是集合式,另一類就是棋盤式(即基於連通性)。

  其中,集合式狀壓DP難度相較後者略大,形式複雜多變。而棋盤式狀壓DP的題目樣式都相差不多,解法也都殊途同歸,因此將這一類題進行總結,不難歸結出一套解題模板。

正文

  我們先看以下三個例題。

國王

題目連結

演算法概述

  狀態表示:f[i,j,s]表示已經在前i行放了j個國王,且第i行的狀態為s的方案數。

  狀態轉移:考慮第i-1行所有合法狀態x,則f[i,j,s]=∑f[i-1,j-count(s),x],其中count(s)表示s中國王的個數。

  演算法流程:

  1.考慮將一行的狀態進行壓縮,然後預處理所有合法狀態。

  2.列舉所有合法狀態,對於每一種合法狀態,處理該狀態可由哪些狀態轉移過來。

  3.迴圈列舉DP狀態,進行狀態轉移。

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=12;

int stt[1<<N];
int cnt[1<<N];
int pre[1<<N][1<<N];
int sum[1<<N];
ll f[N][N*N][1<<N];
int n,m,cnt_stt;

bool check(int state)
{
    for(int i=0;i<n-1;i++)
        if(state>>i&1 && state>>i+1&1)return false;
    return true;
}

int count(int state)
{
    int ans=0;
    for(int i=0;i<n;i++)
        if(state>>i&1)ans++;
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    
    for(int i=0;i<(1<<n);i++)if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i);
    
    for(int i=1;i<=cnt_stt;i++)
        for(int j=1;j<=cnt_stt;j++)
            if((stt[i]&stt[j])==0 && check(stt[i]|stt[j]))pre[stt[i]][++sum[stt[i]]]=stt[j];
    
    f[0][0][0]=1;
    for(int i=1;i<=n+1;i++)
        for(int j=0;j<=m;j++)
            for(int s=1;s<=cnt_stt;s++)
            {
                int a=stt[s];
                if(j<cnt[a])continue;
                for(int x=1;x<=sum[a];x++)
                {
                    int b=pre[a][x];
                    f[i][j][a]+=f[i-1][j-cnt[a]][b];
                }
            }
    
    printf("%lld\n",f[n+1][m][0]);
    
    return 0;
}

玉米田

題目連結

演算法概述

  狀態表示:f[i,a]表示已經種了前i行,且第i行的狀態為a的方案數。

  狀態轉移:考慮第i-1行的所有合法狀態b,則f[i,a]=∑f[i-1,b]。

  演算法流程:

  1.預處理所有合法狀態。

  2.預處理對於每一個合法狀態,能夠轉移到其的所有合法狀態的集合。

  3.迴圈狀態表示,考慮當前行狀態是否與地圖限制相矛盾,然後迴圈狀態轉移。

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=14,M=1<<12,mod=1e8;

int stt[1<<N];
int cnt[1<<N];
int pre[M][M];
int f[N][1<<N];
int g[N];
int n,m,cnt_stt;

bool check(int state)
{
    for(int i=0;i<m-1;i++)
        if(state>>i&1 && state>>i+1&1)return false;
    return true;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=0;j<m;j++)
        {
            int x;scanf("%d",&x);
            g[i]+=!x<<j;
        }
    
    for(int i=0;i<(1<<m);i++)if(check(i))stt[++cnt_stt]=i;
    
    for(int i=1;i<=cnt_stt;i++)
        for(int j=1;j<=cnt_stt;j++)
            if((stt[i]&stt[j])==0)pre[stt[i]][++cnt[stt[i]]]=stt[j];
            
    f[0][0]=1;
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=cnt_stt;j++)
        {
            int a=stt[j];
            if(g[i]&a)continue;
            for(int s=1;s<=cnt[a];s++)
            {
                int b=pre[a][s];
                (f[i][a]+=f[i-1][b])%=mod;
            }
        }
    
    printf("%d\n",f[n+1][0]);
    
    return 0;
}

炮兵陣地

題目連結

演算法概述

  狀態表示:

  狀態轉移:

  演算法流程:

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=110,M=11;

int stt[1<<M];
int cnt[1<<M];
int f[2][1<<M][1<<M];
int g[N];
int n,m,cnt_stt;

bool check(int state)
{
    for(int i=0;i<=m-2;i++)
        if((state>>i&1) && (state>>i+1&1 || state>>i+2&1))return false;
    return true;
}

int count(int state)
{
    int ans=0;
    for(int i=0;i<m;i++)ans+=state>>i&1;
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        static char ch[M];
        scanf("%s",ch);
        for(int j=0;j<m;j++)
            if(ch[j]=='H')g[i]+=1<<j;
    }

    for(int i=0;i<(1<<m);i++)
        if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i);

    for(int i=1;i<=n+2;i++)
        for(int j=1;j<=cnt_stt;j++)
            for(int k=1;k<=cnt_stt;k++)
            {
                int a=stt[j],b=stt[k];
                if(g[i-1]&a|g[i]&b)continue;
                for(int x=1;x<=cnt_stt;x++)
                {
                    int c=stt[x];
                    if((a&b)|(a&c)|(b&c))continue;
                    f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][c][a]+cnt[b]);
                }
            }
    
    printf("%d\n",f[n+2&1][0][0]);
    
    return 0;
}

模型總結