轮廓线DP

这个怎么说呢?就是状压\(DP\)优化,直接在例题里讲吧。

例题:P5005 中国象棋 - 摆上马

我们把题目的规则改一下。

  1. 删去马蹩脚的规则,变成国际象棋的规则。

  2. 更改数据范围,\(1 \le X \le 100,1 \le Y \le 8\)

这种数据范围就不能使用常规的状压DP了,这个时候,轮廓线DP闪亮登场!

做法:

轮廓线的思想是逐格推进,也就是像双指针或滑动窗口一样。
fuck

这个黄色点就是当前枚举到的位置,而\(1\)号点就是他最远能攻击到的格子,那么哪些格子能被攻击呢?

就是\(1,3,8,12\)

可是后面这么一大堆格子有什么用呢?

这样可以方便我们来传递答案!

我们只需要关心轮廓线上方可能对当前格子产生攻击威胁的历史状态。

当我们在 \((x,y)\) 准备放马时,可能对我们产生威胁的历史格子有 \(4\) 个:

  1. 左上 \(2\) 步: \((x - 1, y - 2)\)
  2. 右上 \(2\) 步: \((x - 1, y + 2)\)
  3. 上上左 \(1\) 步: \((x - 2, y - 1)\)
  4. 上上右 \(1\) 步: \((x - 2, y + 1)\)

这么看来我们最远的威胁距离就是 \(2Y + 1\)

所以我们只需要枚举 \(2Y + 1\) 的状态即可。

状态定义与转移方式
状态定义: \(dp_{i,j,s}\) 表示当前坐标为 \((i, j)\) 的格子,且过去 \(2Y + 1\) 个格子的放置状态为 \(s\) 的合法方案数。

使用滑动窗口的思想:

那么下一步就是
fuckccf

决策 1:当前格不放马 (0)
无论历史状态如何,当前格不放马绝对合法。
新状态 s' = (1 << s) & maskmask用于截断超出的高位)。
决策 2:当前格放马 (1)
前提是历史状态 \(s\) 中,对应那 \(4\) 个威胁位上都不是 \(1\)
如果合法,新状态 s' = ((s << 1) | 1) & mask

好了剩下就简单了。

代码

戳我喵~
const int mod = 1e9 + 7;

int dp[110][9][(1 << 17) + 10]; // i, j, s 表示放了 (i, j) 且轮廓线总状态 s , (i, j) 为 0 号
int n, m, M; 
int dx[] = {-1, -2, -2, -1};
int dy[] = {-2, -1, 1, 2};

int get_id(int x, int y) {return (x - 1) * m + y - 1;}

int get_s(int s, int now_x, int now_y, int x, int y) { 
	//当前要放的棋子在 (now_x,now_y), 来判断 (x,y) 位置有没有棋子
	int now_id = get_id(now_x, now_y), id = get_id(x, y), delta = now_id - id - 1;
	return s & (1 << delta);
	return (s >> delta) & 1;
}

signed main() {
	n = re, m = re, dp[1][1][1] = dp[1][1][0] = 1, M = (1 << (2 * m + 1)) - 1;
	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) for (int s = 0; s <= M; s++) if (j + 1 <= m) {
		//下一个位置 (i, j + 1) 不放
		(dp[i][j + 1][(s << 1) & M] += dp[i][j][s]) %= mod; 
		//下一个位置 (i, j + 1) 放
		int flag = 1;
		for (int k = 0; k < 4; k++) {
			int x = i + dx[k], y = j + 1 + dy[k];
			if (x >= 1 && x <= n && y >= 1 && y <= m && get_s(s, i, j + 1, x, y)) {
				flag = 0;
				break;
			}
		}
		if (flag) (dp[i][j + 1][(s << 1 | 1) & M] += dp[i][j][s]) %= mod; 
	} else {
		//下一个位置 (i, 1) 不放
		(dp[i + 1][1][(s << 1) & M] += dp[i][j][s]) %= mod;
		//下一个位置 (i, 1) 放
		int flag = 1;
		for (int k = 0; k < 4; k++) {
			int x = i + 1 + dx[k], y = 1 + dy[k];
			if (x >= 1 && x <= n && y >= 1 && y <= m && get_s(s, i + 1, 1, x, y)) {
				flag = 0;
				break;
			}
		}
		if (flag) (dp[i + 1][1][(s << 1 | 1) & M] += dp[i][j][s]) %= mod;
	}
	int ans = 0;
	for (int s = 0; s <= M; s++) (ans += dp[n][m][s]) %= mod;
	wr(ans), endl;
	return 0; 
}
posted @ 2026-02-26 20:55  OiLight  阅读(24)  评论(0)    收藏  举报