【GDOI三校联考】炸弹 【蒟蒻的小题解】

【GDOI三校联考】炸弹 题解

Description

在一个N行M列的二维网格里,有些格子是空地(用字符‘.’表示),有些格子是障碍物(用字符‘#’表示)。每个空地格子都有一只虫子,虫子不会移动。FJ打算用最少的炸弹把所有的虫子消灭。FJ每次可以选择在一个空地格子(不妨假设是格子a)放置一个炸弹,这个炸弹爆炸后,格子a的虫子会被消灭,假设有另一个空地格子b,如果空地格子b同时满足如下两个条件,那么空地b格子的虫子也会被该炸弹消灭:

1.格子a和格子b在同一行或者在同一列。

2.格子a和格子b之间没有障碍物格子。

有趣的是,任意两个不同的空地格子都有且只有一条由空地格子构成的路径,即所有空地格子构成一棵树形结构。注意:炸弹并不能毁灭障碍物!

Input

第一行,两个整数,n和m。1 <= n, m<=50。
接下来是n行m列的二维网格。

Sample Input

输入1:
3 4
#…
…##
#.##

输入2:
3 7
.#.#.#.

.#.#.#.

Output

输出最少的炸弹数。

Sample Output

输出1:
2
输出2:
4

分析

因为题目说空地形成一颗可爱的小树,所以我们很容易 想到树形DP,但是要怎么建树呢?我们考虑把一个只能延伸向一个方向的点作为根节点。以样例1为例【GDOI三校联考】炸弹 【蒟蒻的小题解】_第1张图片
就是把某个点可以炸到的点都变成那个点的儿子。

然后考虑DP

我们设 f x , 0 f_{x,0} fx,0 f x , 1 f_{x,1} fx,1 f x , 2 f_{x,2} fx,2分别表示当前点没被炸到,没放炸弹但被炸到,放了炸弹三种状态,接着我们考虑转移。

f [ x ] [ 0 ] = ∑ f [ s o n ] [ 1 ] f[x][0]=\sum f[son][1] f[x][0]=f[son][1]
f [ x ] [ 1 ] = m i n ( f [ p ] [ 2 ] + ∑ m i n ( f [ s o n ] [ 0 ] , f [ s o n ] [ 1 ] , f [ s o n ] [ 2 ] ) ) f[x][1]=min(f[p][2]+\sum min(f[son][0],f[son][1],f[son][2])) f[x][1]=min(f[p][2]+min(f[son][0],f[son][1],f[son][2]))
f [ x ] [ 2 ] = ( ∑ m i n ( f [ s o n ] [ 0 ] , f [ s o n ] [ 1 ] , f [ s o n ] [ 2 ] ) ) + 1 f[x][2]=(\sum min(f[son][0],f[son][1],f[son][2]))+1 f[x][2]=(min(f[son][0],f[son][1],f[son][2]))+1

解释一下:
x x x是指当前点

f [ x ] [ 0 ] : f[x][0]: f[x][0]:把他的儿子被炸到但没放炸弹的值加起来,应该很好理解吧。。。

f [ x ] [ 1 ] : f[x][1]: f[x][1]:这里的 p p p是指 x x x的所有儿子,而 s o n son son则是除了这个儿子以外的其他儿子,那这个式子就很好理解了吧(不会吧不会吧,不会这都还有人看不懂吧

f [ x ] [ 2 ] : f[x][2]: f[x][2]:把他儿子所有状态加起来,因为他放了炸弹就不用在意儿子的状态。

CODE

这里附上十分丑陋的代码

//L.E.M.T专用水印
#include//万能头NB
using namespace std;
int n,m,rootx,rooty,a[105][105],f[2505][5],v[2505],p[10005][3],total;
int tot,head[2505],next[2505],g[2505];
char ch;
void add(int x,int y)
{
	tot++,head[tot]=y,next[tot]=g[x],g[x]=tot;		
} 
void dfs(int x)//树形DP
{
	for (int i=g[x];i;i=next[i])
	{
		int p=head[i];
		dfs(p);
		f[x][0]+=f[p][1];
		f[x][2]+=min(f[p][1],min(f[p][0],f[p][2]));	
	}
	f[x][2]++;
	f[x][1]=2501;//赋值小一点,不然可能爆int
	for (int i=g[x];i;i=next[i])
	{
		int p=head[i],minn=0;
		for (int j=g[x];j;j=next[j])
		{
			if (p!=head[j])
			{
				int w=head[j];
				minn+=min(f[w][0],min(f[w][1],f[w][2]));
			}
		}
		f[x][1]=min(f[x][1],f[p][2]+minn);
	}
}
void bfs(int xx,int yy)//bfs建树
{
	int t=0,w=1;
	p[w][1]=xx,p[w][2]=yy;
	while (t<w)
	{
		t++;
		int x=p[t][1]-1,y=p[t][2];
		while (a[x][y]>=1&&x>0&&v[t]!=2&&v[t]!=1)
		{
			w++;
			v[w]=1;
			p[w][1]=x;
			p[w][2]=y;
			add(a[p[t][1]][p[t][2]],a[x][y]);
			x--;	
		}
		x=p[t][1]+1,y=p[t][2];
		while (a[x][y]>=1&&x<=n&&v[t]!=1&&v[t]!=2)
		{
			w++;
			v[w]=2;
			p[w][1]=x;
			p[w][2]=y;
			add(a[p[t][1]][p[t][2]],a[x][y]);
			x++;
		}
		x=p[t][1],y=p[t][2]-1;
		while (a[x][y]>=1&&y>0&&v[t]!=4&&v[t]!=3)
		{
			w++;
			v[w]=3;
			p[w][1]=x;
			p[w][2]=y;
			add(a[p[t][1]][p[t][2]],a[x][y]);
			y--;
		}
		x=p[t][1],y=p[t][2]+1;
		while (a[x][y]>=1&&y<=m&&v[t]!=3&&v[t]!=4)
		{
			w++;
			v[w]=4;
			p[w][1]=x;
			p[w][2]=y;
			add(a[p[t][1]][p[t][2]],a[x][y]);
			y++;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		ch=getchar();
		while (ch!='.'&&ch!='#')
			ch=getchar();
		if (ch=='.')
		{
			total++;
			a[i][1]=total;
		}
		else
		{
			a[i][1]=0;
		}
		for (int j=2;j<=m;j++)
		{
			ch=getchar();
			if (ch=='.')
			{
				total++;
				a[i][j]=total;
			}
			else
			{
				a[i][j]=0;
			}
		}
	}
	rootx=0,rooty=0;
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			if (a[i][j]>=1)
			{
				int bz=0;
				if (a[i-1][j]>=1)
					bz++;
				if (a[i+1][j]>=1)
					bz++;
				if (a[i][j-1]>=1)
					bz++;
				if (a[i][j+1]>=1)
					bz++;
				if  (bz==1)//寻找根节点
				{
					rootx=i;
					rooty=j;
					break;
				}
			}
		}
		if (rootx!=0)
		{
			break;
		}
	}
	bfs(rootx,rooty);
	dfs(a[rootx][rooty]);
	printf("%d",min(f[a[rootx][rooty]][1],f[a[rootx][rooty]][2]));
}

建树十分丑陋,大家能不看就不看吧,不过树形DP还是可以借鉴一下的。

你可能感兴趣的:(题解,树形DP,dfs,编程语言)