题解 luogu.P1879 [USACO06NOV] Corn Fields G
题目
luogu.P1879 [USACO06NOV] Corn Fields G
本题是一道状压DP的经典好题。由于初学,不是很熟,所以研究需较为深入。
题意建模
序言
本题与笔者所写的另一篇题解所研究的题目有相似之处:
题解 luogu.P1896 [SCOI2005] 互不侵犯
本质上都是基于连通性的DP,换句话说,由于上一题太过出名,这类题型又称棋盘类状压DP。
我们来看一下本题的做法。
建模
观察数据范围,很显然,要状压;又因为状态的表示是 bool 类型,考虑使用0/1表示状态。
算法分析
状态定义
\(f(i,j)\) 表示当前处理到第 \(i\) 行,第 \(j\) 个状态所合计的方案数。
状态转移
\(f(i,j)= \sum_{j\in i,k\in i-1 } f(i-1,k)\)
这里阶段划分很明显,所以不拿出来细讲,显然是以行为状阶段转移,所以当前行的方案数累加上上一行的就可以。
边界情况与合法判断
这类似与搜索剪枝,所以除了状态定义之外,这一步是最关键的,也是最难的。我们先看边界情况。
边界情况
首先,都从1开始存储信息,所以为了保持代码风格一致以及逻辑的连贯性,我们定义状态的边界也从1开始。所以应有:dp[0][1]=1。这个的实际意义是:什么都不做处理,也算一种方案。
合法判断
- 相邻行内,不能存在相邻的草地,即:
!(x&y) && (x&g[x])==x必须为真。 - 同一行内,不能存在相邻的草地。即:
!(i&i<<1)必须为真。
这样,代码的大体框架就出来了。
参考代码
#include<iostream>
#define int long long
using namespace std;
const int N=15,mod=1e8;
int n,m,dp[N][1<<N];
int s[1<<N],g[N],cnt;
signed main()
{
cin>>n>>m;
int all=(1<<m)-1;//同一行的总状态数
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x; cin>>x;
g[i]=(g[i]<<1)+x;//统计地图
}
for(int i=0;i<=all;i++)
if(!(i&i<<1))
s[++cnt]=i;//统计合法的情况(行内)
dp[0][1]=1;
for(int i=1;i<=n+1;i++)
for(int j=1;j<=cnt;j++)
if((s[j]&g[i])==s[j])//行间合法
for(int k=1;k<=cnt;k++)
if(!(s[k]&s[j]))//同上,但是分开写效率更高
(dp[i][j]+=dp[i-1][k])%=mod;
cout<<dp[n+1][1];
//多存一行,这样枚举到n+1的时候,所有状态都已枚举完毕,这等效于最后单独统计答案。
return 0;
}
细节实现
好,我们关注几个细节。
- 首先,这个题读入有坑。
- 其次,数组的大小记得开够,最好是能多出3~5个单元,不然越界差错非常困难
- 最重要的是,关注边界条件,下标和循环的范围是有着状态的实际含义的。
总结归纳
多积累经验,多做经典题,多思考好题,多总结,多提升。

浙公网安备 33010602011771号