在一个N行M列的二维网格里,有些格子是空地(用字符‘.’表示),有些格子是障碍物(用字符‘#’表示)。每个空地格子都有一只虫子,虫子不会移动。FJ打算用最少的炸弹把所有的虫子消灭。FJ每次可以选择在一个空地格子(不妨假设是格子a)放置一个炸弹,这个炸弹爆炸后,格子a的虫子会被消灭,假设有另一个空地格子b,如果空地格子b同时满足如下两个条件,那么空地b格子的虫子也会被该炸弹消灭:
1.格子a和格子b在同一行或者在同一列。
2.格子a和格子b之间没有障碍物格子。
有趣的是,任意两个不同的空地格子都有且只有一条由空地格子构成的路径,即所有空地格子构成一棵树形结构。注意:炸弹并不能毁灭障碍物!
第一行,两个整数,n和m。1 <= n, m<=50。
接下来是n行m列的二维网格。
输入1:
3 4
#…
…##
#.##
输入2:
3 7
.#.#.#.
…
.#.#.#.
输出最少的炸弹数。
输出1:
2
输出2:
4
因为题目说空地形成一颗可爱的小树,所以我们很容易 想到树形DP,但是要怎么建树呢?我们考虑把一个只能延伸向一个方向的点作为根节点。以样例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]:把他儿子所有状态加起来,因为他放了炸弹就不用在意儿子的状态。
这里附上十分丑陋的代码
//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还是可以借鉴一下的。