【题目链接】
ybt 1593:【例 2】牧场的安排
洛谷 P1879 [USACO06NOV] Corn Fields G
【题目考点】
1. 状压动规
【解题思路】
集合状态:n个元素中,选择x个元素构成的集合,可以由一个n位二进制数表示。第i位为1表示选择第i个元素,第i位为0表示不选择第i个元素。
将在某格子种草称为为该格子着色。
本题每行着色的情况可以设为一个集合状态,称为着色状态。第i位为1表示选择了这一行的第i个格子已着色,第i位为0表示第i个格子没有着色。
设sis_isi表示第i行的土地状态,sis_isi第j位为1表示第i行第j个格子可以着色,为0表示不可以着色。
二进制下的数字位,从0开始,从低位到高位分别是第0位,第1位。。。
例:二进制数:110 :第0位为0, 第1位为1,第2位为1
解法1:状压动规 枚举所有状态
状态定义
- 阶段:前i行,第i行的着色为集合状态j
第i+1行的集合状态受到第i行着色状态的影响,因此阶段之一需要是第i行的着色状态。 - 决策:决定一行的着色
- 策略:网格图的着色方案
- 策略集合:对前i行着色,且第i行着色为集合状态j的所有着色方案
- 统计量:方案数
状态定义dpi,jdp_{i,j}dpi,j:对前i行着色,且第i行着色为集合状态j的着色方案总数。
状态转移方程
- 策略集合:对前i行着色,且第i行着色为集合状态j的所有着色方案
- 分割策略集合:根据第i-1行的不同着色状态来分割策略集合
在求dpi,jdp_{i,j}dpi,j时,要枚举第i行的所有着色状态j,从中筛选出符要求的着色状态。在此过程中需要枚举第i-1行所有可能的着色状态,设枚举出的第i-1行的着色状态为k。
-
着色的格子必须是这一行可以着色的格子的子集
因此第i行的着色状态j表示的已着色的格子必须是sis_isi表示的已着色的格子的子集,即必须满足
s[i] & j == j
。
第i-1行的着色状态k表示的已着色的格子必须是si−1s_{i-1}si−1表示的已着色的格子的子集,即必须满足s[i-1] & k == k
。 -
每一行不可以有相邻的两个格子都着色。
将k左移一位k<<1
,k<<1
中第i位数为k
中第i-1位数。k<<1
的最低位为0。
那么k<<1 & k
的结果,就是将k的每一位与其右侧相邻一位进行按位与运算,(由于k<<1
最低位为0,那么按位与的结果最低位为0)
如果存在k的相邻两位都是1,那么结果不为0。如果不存在k的相邻两位都是1,那么结果为0。
因此第i-1行只有满足(k<<1 & k) == 0
(或写为!(k<<1 & k)
)的着色状态符合要求。同理第i行只有满足!(j << 1 & j)
的着色状态符合要求。 -
第i行的着色状态j与第i-1行的着色状态k同一列的格子不能都着色。
也就是这两个二进制数同一数位上不能都为1。
如果k与j某一位都为1,那么k & j
不为0,否则为0。
因此k与j需要满足(k & j) == 0
(或写为!(k & j)
),才不存在第i行与第i-1行纵向上有相邻的格子都着色。
对于第i行的符合要求的着色状态j,找到一个符合要求的第i-1行的着色状态k,对前i-1行着色且第i-1行的着色状态为k的着色方案数为dpi−1,kdp_{i-1,k}dpi−1,k。
对i-1行所有满足条件的着色状态k,将dpi−1,kdp_{i-1,k}dpi−1,k加和,即为对前i行着色且第i行着色状态为j的着色方案数。
因此dpi,j=∑dpi−1,kdp_{i,j} = \sum dp_{i-1,k}dpi,j=∑dpi−1,k,j需要满足j & s[i] == j
,k需要满足k & s[i-1] == k && !(k<<1 & k) && !(k & j)
。
对于初始状态,本题可以枚举第一行满足没有相邻格子染色(即满足j & s[1] == j && !(j<<1 & j)
)的染色状态j,第一行染色状态为j的方案有一种,设dp1,j=1dp_{1,j} = 1dp1,j=1。
或者假设第0行参与染色,第0行只存在染色状态为0的一种情况,因此设dp0,0=1dp_{0,0}=1dp0,0=1
最后对于第m行的所有符合j & s[m] == j
的状态j,求出为前m行染色,第m行着色状态为j的方案数,并加和。即结果为∑dpm,j\sum dp_{m,j}∑dpm,j,j满足j & s[m] == j
。
注意,每次加和后都要对10810^8108取模。
解法2:状压动规 保存合法的状态
本题每一行只会取没有相邻格子着色的状态,那么可以先将这些一定没有相邻格子同时着色的集合状态预处理出来,保存在state数组,state[i]
表示第i个可用的集合状态。
而后dpi,jdp_{i,j}dpi,j表示的是对前i行着色,且第i行着色状态为state[j]
的方案数。
接下来在判断着色状态是否符合要求时,就不用判断“要求相邻格子不能同时着色”这一点了,即不用再写j<<1 & j
与k<<1 & k
。
【题解代码】
解法1:状压动规 枚举所有状态
- 写法1:设第1行的状态作为初始状态
#include<bits/stdc++.h>
using namespace std;
const int N = 12+5, S = (1<<12)+5, M = 1e8;
int m, n, s[N];//s[i]:第i行可以种草的集合状态 1表示可以种,0表示不可以种
long long dp[N][S], ans;//dp[i][j]:前i行着色,第i行集合状态为j的方案数
int main()
{int x;cin >> m >> n;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j){cin >> x;s[i] = s[i]<<1 | x;}for(int j = 0; j < 1<<n; ++j) if((j & s[1]) == j && !(j & j<<1))//j不是s[i]的子集 或 j有相邻的着色格 dp[1][j] = 1;for(int i = 2; i <= m; ++i)for(int j = 0; j < 1<<n; ++j) if((j & s[i]) == j && !(j & j<<1))//j是s[i]的子集 同时 j没有相邻的着色格 for(int k = 0; k < 1<<n; ++k) if((k & s[i-1]) == k && !(k & k<<1) && !(k & j))//k不是s[i-1]的子集,或k有相邻着色格,或k与j有上下相邻的着色格 dp[i][j] = (dp[i][j]+dp[i-1][k])%M; for(int j = 0; j < 1<<n; ++j) if((j & s[m]) == j && !(j & j<<1))ans = (ans+dp[m][j])%M;cout << ans;return 0;
}
- 写法2:设第0行的状态作为初始状态
#include<bits/stdc++.h>
using namespace std;
const int N = 12+5, S = (1<<12)+5, M = 1e8;
int m, n, s[N];//s[i]:第i行可以种草的集合状态 1表示可以种,0表示不可以种
long long dp[N][S], ans;//dp[i][j]:前i行着色,第i行集合状态为j的方案数
int main()
{int x;cin >> m >> n;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j){cin >> x;s[i] = s[i]<<1 | x;}dp[0][0] = 1;for(int i = 1; i <= m; ++i)for(int j = 0; j < 1<<n; ++j) if((j & s[i]) == j && !(j & j<<1))//j是s[i]的子集 同时 j没有相邻的着色格 for(int k = 0; k < 1<<n; ++k) if((k & s[i-1]) == k && !(k & k<<1) && !(k & j))//k不是s[i-1]的子集,或k有相邻着色格,或k与j有上下相邻的着色格 dp[i][j] = (dp[i][j]+dp[i-1][k])%M; for(int j = 0; j < 1<<n; ++j) if((j & s[m]) == j && !(j & j<<1))ans = (ans+dp[m][j])%M;cout << ans;return 0;
}
解法2:状压动规 保存合法的状态
#include<bits/stdc++.h>
using namespace std;
const int N = 12+5, S = (1<<12)+5, M = 1e8;
int m, n, s[N];//s[i]:第i行可以种草的集合状态 1表示可以种,0表示不可以种
int state[S], sn;//state[i]:第i个符合条件的集合状态
long long dp[N][S], ans;//dp[i][j]:前i行着色,第i行集合状态为state[j]的方案数
int main()
{int x;cin >> m >> n;for(int i = 1; i <= m; ++i)for(int j = 1; j <= n; ++j){cin >> x;s[i] = s[i]<<1 | x;}for(int i = 0; i < 1<<n; ++i) if((i & i<<1) == 0)state[++sn] = i;//把没有相邻的着色格的集合状态加入state for(int j = 1; j <= sn; ++j) if((state[j] & s[1]) == state[j])dp[1][j] = 1;for(int i = 2; i <= m; ++i)for(int j = 1; j <= sn; ++j) if((state[j] & s[i]) == state[j])//j不是s[i]的子集 for(int k = 1; k <= sn; ++k) if((state[k] & s[i-1]) == state[k] && !(state[k] & state[j]))//k不是s[i-1]的子集,或k有相邻着色格,或k与j有上下相邻的着色格 dp[i][j] = (dp[i][j]+dp[i-1][k])%M;for(int j = 1; j <= sn; ++j) if((state[j] & s[m]) == state[j])ans = (ans+dp[m][j])%M;cout << ans;return 0;
}