思路
对于状态多,决策较少(一般是两个)的问题,我们可以利用二进制去表示其状态,很多棋盘问题都会用到状压,一般求解会用到搜索
P1879 [USACO06NOV]Corn Fields G
思路
首先值为1的地方可以种草,同时种草的地方满足上下左右无相邻\
令f[i][j]表示第i行状态为j时的答案数
那我们用init[]存放每一行的地图,若j为当前状态,要满足( j & init[] ) == j
预处理同一行满足条件的状态 legal[i],其中(i & (i << 1) ) == 0 && ( i & (i >> 1) ) == 0。(无相邻
那么我们在求第i行时,暴力枚举所有可能情况,令j为i行状态,则j = 0 to maxn,然后对于每个j,r若满足(j & init[i]) == j && legal[j],现在满足了在1上面种草并且同一行无相邻, 我们去枚举i - 1行的情况,令k为i - 1行的状态,k = 0 to maxn,若 !(j & k) ,则该状态满足条件。找到所有的满足条件的k,则f[i][j] = Sigma f[i-1][k](很好理解,然后j 跑完,该行处理完成了,进入下一行
注意,最后答案是Sigma f[n][i]
代码
#include<bits/stdc++.h>
#define Mod (long long)1e8
#define ll long long
using namespace std;
ll n, m, maxn, ans;
ll init[30], legal[30], f[13][1 << 13];
int main(){
cin >> n >> m;
for(int i = 1;i <= n ;i ++)
for(int j = 1; j <= m;j ++){
ll temp;
cin >> temp;
init[i] = (init[i] << 1) + temp;
}
ll maxn = (m << 1) - 1;//全为1
for(int i = 0;i <= maxn;i ++)
if( (i & (i << 1)) == 0 && (i & (i >> 1)) == 0 )
legal[i] = 1;//预处理一行没有相邻的情况
f[0][0] = 1;
for(int i = 1; i <= n;i ++)//行
for(int j = 0;j <= maxn;j ++){//j枚举第i行的情况
if(legal[j] && (j & init[i]) == j){
for(int k = 0;k <= maxn;k ++)
if(!(k & j))
f[i][j] = (f[i][j]%Mod + f[i-1][k] % Mod) % Mod;
}
}
for(int i = 0;i <= maxn;i ++)
ans = (ans%Mod + f[n][i]%Mod) % Mod;
cout << ans%Mod;
return 0;
}
P1896 [SCOI2005]互不侵犯
这里有一个k个国王的条件,因此需要多开一维
设f[i][j][s]为选到第i行,状态为j时,用了s个国王的方案数
则f[i][j][s] = Sigma f[i - 1][k][s - sum[j]],sum[]来存每个状态的国王数,num[]存了cnt数量的可行的状态。这里使用预处理提前存好了状态,替换了 for(int j = 0;j <= maxn;j ++)语句
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n, k, maxn, ans, cnt;
ll sum[1 << 11], num[1 << 11], f[20][1 << 11][20];
void init(){
maxn = (1 << n) - 1;
for(int i = 0 ;i <= maxn;i ++){
if(!(i & (i << 1)) && !(i & (i >> 1))){//一行中无相邻
int s = 0;//统计这个状态的国王数量
for(int t = 0;(1 << t) <= i;t ++)
if((1 << t) & i)
s ++;
num[++cnt] = i;//记录成立的状态
sum[cnt] = s;//该状态所用国王数
}
}
}
int main(){
cin >> n >> k;
init();
f[0][0][0] = 1;
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= cnt;j ++){//i的状态为num[j]
for(int s = 0; s <= k; s ++){
if(s < sum[j])continue;//s要大于等于此状态要用的棋子数量
for(int z = 1; z <= cnt; z ++)//枚举i-1的状态num[z]
if(!((num[j] << 1) & num[z]) && !((num[j] >> 1) & num[z]) && !(num[j] & num[z]))
f[i][num[j]][s] += f[i-1][num[z]][s - sum[j]];
}
}
}
for(int i = 1;i <= cnt;i ++)
ans += f[n][num[i]][k];
cout << ans;
return 0;
}