状压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;
}
原文:https://www.cnblogs.com/ninedream/p/13404965.html