合理安排(状压dp,包括技巧判断子集)
花园主John新买了一块长方形的花园,这块花园被精心地划分成M行N列(1≤M≤12,1≤N≤12),每一格都是一块正方形的花坛。John计划在花园的某些花坛里种上鲜艳的花朵,让他的蜜蜂们能够采集花蜜。然而,有些花坛的土壤质量不佳,不适合种植花朵。另外,为了保持花园的美观和蜜蜂的舒适度,John不会选择相邻的花坛种植花朵,也就是说,没有哪两个花坛是相邻的(即它们没有公共边)。John想知道,如果不考虑花朵的总数,那么,他有多少种不同的种植方案可以选择?(当然,将花园完全保持原样不种植花朵也是一种方案)
输入格式
第一行:两个整数M和N,用空格隔开,分别代表花园的行数和列数。
第2到第M+1行:每行包含N个用空格隔开的整数,描述了每个花坛的土壤状态。第i+1行描述了第i行的花坛状态,所有整数均为0或1,其中1表示花坛土壤肥沃适合种植花朵,0则表示土壤不适合种植。
1<=M<=12,1<=N<=12
输出格式
一个整数,即牧场分配总方案数除以100000000的余数。
输入/输出例子1
输入:
2 3
1 1 1
0 1 0
输出:
9
样例解释
无
注意到范围很小,且和dp可能扯上关系
考虑状压。
但是题目给了一些限制条件
1.对于同一行,相邻列不同时为1,开个循环判断即可,假设本行选了状态是s, (s<<i) & (s<<(i+1)) ==0,s的第 i 位和第 i+1 位不同时为1即可。
2.对于不同行,相同列,相邻行不同时为1,这个更简单了,假设第一行选了状态是x,第二行选了状态是y,x & y == 0
然后我们可以预处理每一行的合法状态,装入vec,这样可以减少枚举量,也方便一些
状态和转移就显而易见了。
f(i, s) 当前考虑前i行,并且第i行的状态是s的方案数
假设s2是第i行选的状态,s1是第i-1行选的状态。满足限制条件时(只有限制2,因为这是对于不同行时的转移),f(i, s2) += f(i-1, s1)
如何预处理合法状态?这里给一个小知识点
判断一个集合是否是另外一个集合的子集
s2 & s == s2
s2是s的子集
所以我们先处理每一行合法状态的最大值(就是每一行,可以种植的地,对应的二进制和)
如何枚举二进制状态,前提是每个二进制状态是合法状态最大值的子集才行,如何因为这是同一行,就只有限制1了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=15, M=8500, Mod=100000000;
int n, m, a[N][N], f[N][M], ans=0;
vector<int> v[N];
bool check(int x)
{
for (int i=0; i<=12; i++)
if (((x<<i) & (x<<(i+1)))!=0) return 0;
return 1;
}
signed main()
{
scanf("%lld%lld", &n, &m);
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
scanf("%lld", &a[i][j]);
for (int i=1; i<=n; i++)
{
int s=0;
for (int j=1; j<=m; j++)
if (a[i][j]) s+=(1<<(j-1));
for (int j=0; j<(1<<m); j++)
if ((j&s)==j && check(j)) v[i].push_back(j);
}
for (int i=0; i<v[1].size(); i++) f[1][v[1][i]]=1;
for (int i=2; i<=n; i++)
for (int j=0; j<v[i].size(); j++)
for (int k=0; k<v[i-1].size(); k++)
if ((v[i][j]&v[i-1][k])==0) f[i][v[i][j]]=(f[i][v[i][j]]+f[i-1][v[i-1][k]])%Mod;
for (int i=0; i<v[n].size(); i++) ans=(ans+f[n][v[n][i]])%Mod;
printf("%lld", ans);
return 0;
}

浙公网安备 33010602011771号