[jzoj3252] 【GDOI三校联考】炸弹(经典DP)

Problem

  • 给出一个网格图,由010101构成,其中000相连,构成一颗树.

  • 可以放炸弹,一个炸弹可以覆盖连续的一行000或一列000.

  • 求放至少多少个炸弹,使得每个000都会被至少111个炸弹覆盖。

Data constraint

  • n,m≤50n,m\le 50n,m50

Solution

  • 数据范围很小,加起来就250025002500个点,很容易让人误入歧途.

  • 正解实质上是DP

  • 以从左到右,从上到下的顺序找到第一个000,作为根,依次走下去,形成一颗树.

  • 考虑每个格子(x,y)(x,y)(x,y)的状态,不妨假设其父亲格点是(x−1,y)(x-1,y)(x1,y)

    • ①:直接在(x,y)(x,y)(x,y)这个点放了一个炸弹.
    • ②:由(x,y−1),(x,y+1)(x,y-1),(x,y+1)(x,y1),(x,y+1)转移过来,即xxx行放了炸弹,且能影响到(x,y)(x,y)(x,y).
    • ③:由(x+1,y)(x+1,y)(x+1,y)转移过来,即在第yyy列放了炸弹,且能影响到(x,y)(x,y)(x,y).
  • 为何要把第二、三种分开讨论?

  • 显然,这是因为(x+1,y)(x+1,y)(x+1,y)的影响不仅仅是(x,y)(x,y)(x,y),可能会影响到它父亲,父亲的父亲……

  • 所以,我们首先要把一个点的状态给分析清楚,即可能有怎样的决策影响.

  • 分析好状态,我们不难想到这样的状态设计:

    • f[i][0]f[i][0]f[i][0]:表示以iii为根的子树,没放炸弹,还剩一条与父亲向同向的子树链没有炸.
    • f[i][1]f[i][1]f[i][1]:表示以iii为根的子树,子树炸完了,但没有炸出去,即父亲向的方向上没有放炸弹.
    • f[i][2]f[i][2]f[i][2]:表示以iii为根的子树,子树炸完了,且炸出去了,即父亲向的方向上有炸弹.
  • 如此一来,我们便可以很方便的讨论转移了.

  • 具体转移分成四类,即有0,1,2,30,1,2,30,1,2,3个儿子时的情况.

  • 这里以333个儿子为例,其他是类似的,且333个儿子也是最复杂的。

不妨设目前转移点编号为k
其中p[1],p[2],p[3]为k的3个儿子,且p[1]在父亲向(上下)的方向上,p[2],p[3]在k点的左右方向上.

那么必定有:

f[k][0] = min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2], 
			   Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2]
			  ) + f[p[1]][0];

//f[p[1]][0]是必须要转移的,因为当前点没放炸弹,如果要延续,则儿子必须连续
//因为当前点没有放炸弹,所以“左右”方向上必定有一个是炸出去了的,也就是f[p[2]][2]或f[p[3]][2]必选一个
//另外一个则可以随便选,所以是取3个min值.

f[k][1] = min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2],
			   Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2]
			   ) + f[p[1]][1];

//f[p[1]][1]是必须要转移的,因为当前点也不能放炸弹.
//其余与上面一种情况完全类似.

f[k][2] = min( (1 + Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) +
					Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) +
					Min(f[p[3]][0], f[p[3]][1], f[p[3]][2])), 
				0 + f[p[1]][2] +
				min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2],
				     Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2])
			);
}

//这里的转移就分两种情况讨论了,即当前点放炸弹还是不放.
//看一看,应该还是很好理解的.

  • 对于其余的三种情况,其实也并不简单多少,因为还要继续分类讨论,即有没有上下的这种父亲向的儿子.

  • 总之,就是这样的一个DP:

#include <cstdio>
#include <iostream>

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define Min(a, b, c) ((a) < min(b, c) ? a : min(b, c))
#define num(a, b) ((a - 1) * m + b)

const int N = 51, M = 10 * N * N, dx[4] = {0, - 1, 1, 0}, dy[4] = {- 1, 0, 0, 1}, Impossible = 1e9;

using namespace std;

int n, m, tot, Sx, Sy, f[M][3]; char ch[N][N];
int tov[M], nex[M], las[M], Spe[M], Fa[M];

void ins(int x, int y, int z) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, Spe[tot] = z;
}

void Dfs(int k, int La) {
	int SonTotal = 0, p[4]; p[0] = p[1] = p[2] = p[3] = 0;
	for (int x = las[k] ; x ; x = nex[x])
		if (tov[x] ^ La)
			Fa[tov[x]] = Spe[x], Dfs(tov[x], k), p[++ SonTotal] = tov[x];
	if (SonTotal == 0) {
		f[k][0] = 0;
		f[k][1] = Impossible;
		f[k][2] = 1;
	}

	if (SonTotal == 1) {
		if (Fa[p[1]] == Fa[k]) {
			f[k][0] = min(f[p[1]][0], f[p[1]][1]);
			f[k][1] = Impossible;
			f[k][2] = min(min(f[p[1]][0], f[p[1]][1]) + 1, f[p[1]][2]);
		}
		else {
			f[k][0] = f[p[1]][1];
			f[k][1] = f[p[1]][2];
			f[k][2] = Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) + 1;
		}
	}

	if (SonTotal == 2) {
		if (Fa[p[1]] == Fa[k] || Fa[p[2]] == Fa[k]) {
			if (Fa[p[2]] == Fa[k]) swap(p[1], p[2]);

			f[k][0] = min(f[p[2]][1], f[p[2]][2]) + f[p[1]][0];
			f[k][1] = f[p[2]][2] + f[p[1]][1];
			f[k][2] = min( (1 + Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) + Min(f[p[2]][0], f[p[2]][1], f[p[2]][2])),
						   (0 + f[p[1]][2] + min(f[p[2]][1], f[p[2]][2]))  );
		}
			else
		{
			f[k][0] = f[p[1]][1] + f[p[2]][1];
			f[k][1] = min( Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) + f[p[2]][2], Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[1]][2]);
			f[k][2] = 1 + Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) + Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]);
		}
	}

	if (SonTotal == 3) {
		if (Fa[p[2]] == Fa[k]) swap(p[1], p[2]);
		if (Fa[p[3]] == Fa[k]) swap(p[1], p[3]);

		f[k][0] = min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2], Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2]) + f[p[1]][0];
		f[k][1] = min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2], Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2]) + f[p[1]][1];
		f[k][2] = min( (1 + Min(f[p[1]][0], f[p[1]][1], f[p[1]][2]) + Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + Min(f[p[3]][0], f[p[3]][1], f[p[3]][2])), 
						0 + f[p[1]][2] + min( Min(f[p[2]][0], f[p[2]][1], f[p[2]][2]) + f[p[3]][2], Min(f[p[3]][0], f[p[3]][1], f[p[3]][2]) + f[p[2]][2]) );
	}
}

int main() {
	scanf("%d%d", &n, &m);
	F(i, 1, n) scanf("%s", ch[i] + 1);

	F(i, 1, n)
		F(j, 1, m)
		  	if (ch[i][j] == '.') {
				F(k, 0, 3) {
					int x = i + dx[k], y = j + dy[k];
					if (x && y && x <= n && y <= m && ch[x][y] == '.')
						ins( num(x,y), num(i,j), k );
				}
				Sx = Sx == 0 ? i : Sx, Sy = Sy == 0 ? j : Sy;
			}

	Dfs(num(Sx, Sy), 0);

	printf("%d\n", min(f[num(Sx, Sy)][1], f[num(Sx, Sy)][2]));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值