题目链接
题解
这是一道典型的状压dp,对于 \(M\) 行 \(N\) 列的图,由于每个点只有 \(1\) 和 \(0\) 两种状态,我们将其压缩为一个长度为 \(M\) 的数组,数组( \(g\) )的每一项 \(g_{i}\) 表示状态的二进制表示法表示的数(如 \(101\) 表示为 \(5\) ),我们预先处理 \(cnt\) 个可能的状态,将其存放在数组 \(sta\) 当中,我们在用 \(f_{i,j}\) 来表示表示第 \(i\) 行选择状态 \(j\) 的最大情况数
易得到状态转移方程式:
\(
f_{i,j}=
\begin{cases}
f_{i,j}
\\f_{i,j}+f_{i-1,k}
\end{cases}
\)
其中 \(k\) 表示第 \(i-1\) 行的状态为 \(sta_{k}\) ,那么对于状态 \(j\),它存在的条件是:
- 状态 \(j\) 必须可以存在于第 \(i\) 行,即不能有原图上标记为 \(0\) 的点而状态 \(j\) 上标记为 \(1\) 的点,判断代码:
if (sta[i]&(~g[1]));
- 状态 \(j\) 不能与第 \(i-1\) 行的状态 \(k\) 冲突,即状态 \(k\) 上为 \(1\) 的点,状态 \(j\) 上的这个点不能为 \(1\) ,判断代码:
if (sta[k]&sta[j]);
- 同时注意一个细节,在遍历 \(k\) 时,\(k\) 也要符合第 \(i-1\) 行,即:
if (sta[k]&(~g[i-1]));
对于第一行,要单独处理,因为不存在第 \(i-1\) 行。对于最后输出的答案,要把最后一行所有可能的状态的情况数相加才行
int ans=0;
for (int i=1;i<=cnt;i++) ans+=dp[m][i];
CODE
#include<cstdio>
using namespace std;
#define P 100000000
int m,n;
int g[13];
int sta[5000]; //预处理的所有可能的状态
int dp[13][5000]; //dp[i][j]表示第i行如果选择状态j,最大情况数
int cnt;
void dfs(int s,int t){
if (t>=n){
sta[++cnt]=s;
return;
}
dfs(s,t+1); //当第t+1个点不选
dfs(s+(1<<t),t+2); //当第t+1个点选,直接跳转至t+2对t+2+1的点进行选择
}
int main(){
scanf("%d%d",&m,&n);
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
int tmp;
scanf("%d",&tmp);
g[i]=g[i]+(tmp<<(n-j)); //处理每一行的状态(转换为二进制值)
}
}
dfs(0,0); //预处理
for (int i=1;i<=cnt;i++){
if (sta[i]&(~g[1])) continue; //判断状态i对于第一行是否可选
else dp[1][i]++; //如果可选,情况+1
}//第一行特殊处理
for (int i=2;i<=m;i++){
for (int j=1;j<=cnt;j++){
for (int k=1;k<=cnt;k++){
if (sta[j]&(~g[i])) continue; //判断状态i对于第i行是否可选
if (sta[k]&(~g[i-1])) continue; //判断状态k对于第i-1行是否可选
if (sta[k]&sta[j]) continue; //判断状态k和状态j是否可以作为相邻的两行
dp[i][j]+=dp[i-1][k]; //满足上述三个条件后,dp[i][j]加上dp[i-1][k]
}
}
}//第二行之后的处理
int ans=0;
for (int i=1;i<=cnt;i++) ans+=dp[m][i]; //对最后一行所有可能装状态的情况数相加,即为最后的答案
printf("%d",ans%P);
return 0;
}
标签:状态,cnt,sta,int,题解,Fields,USACO06NOV,dp
From: https://www.cnblogs.com/ZYStream/p/18542653