题解 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个单元,不然越界差错非常困难
  • 最重要的是,关注边界条件,下标和循环的范围是有着状态的实际含义的。

总结归纳

多积累经验,多做经典题,多思考好题,多总结,多提升。

posted @ 2025-07-30 10:04  枯骨崖烟  阅读(4)  评论(0)    收藏  举报