算法课作业 5-17世界名画陈列馆问题 回溯

题意重述

给定n(20)和m(20),表示一个n*m的网格,可在格点上放置机器人,机器人会使所在格点和上下左右格点都进入监视状态,问最少放置多少个机器人会使得所有格点都进入监视状态?

解法分析

使用回溯法,从上到下从左到右依次使得每个访问过的格子都进入监视状态.
让未进入监视状态的当前格点进入监视状态只有三种情况,在当前节点放置机器人,在右侧节点放置机器人,在下方节点放置机器人,依次递归判断.
记录初始的最优值为一个最大值,每次放完后与当前最优值比较并更新答案.

实现细节

1.剪枝策略:

  1. 可以证明,放置的机器人个数不会超过nm/3+1个(按每个机器人仅辐射左右或上下考虑,堆叠这样的小长条,最后只会剩下11,12,22三种剩余,都满足).以n*m/3+2为初始最优值,当放置的个数超过当前最优值时,剪去.
  2. 可以证明,当前访问的格点(i,j)已被监视时,放置在(i,j)的情况一定不会比放置在(i+1,j+1)的情况好.当(i+1,j+1)不在网格中时,(i+1,j)和(i,j+1)同理.所以,如果(i,j)已被监视,则不需要在此处放置机器人,直接跳过即可.
    (update,证明:因为是从上到下从左到右使得格子进入监视状态,当前正在检查访问(i,j)。说明在这之前的(i-1,j)和(i,j-1)已被监视。此时如果放在(i,j)处,只会使得(i+1,j)和(i,j+1)进入访问状态。而如果放在(i+1,j+1)处,显然在完成上述目标的情况下可以使更多格子进入访问状态)
  3. 当(i,j)未被监视时,若(i,j+1)已被监视,则在(i,j)放置一定不会比在(i+1,j)放置的情况好.所以当且仅当(i,j)在网格右下角或者(I,j+1)未被监视时才考虑放置在(i,j)的情况.
  4. 当(i,j)未被监视时,若(i,j+1)和(i,j+2)均被监视,则在(i+1,j)放置一定不会比在(i+1,j)放置的情况好,所以当且仅当(i,j+1)或(i,j+2)未被监视时才考虑放置在(i,j+1)的情况.
  5. 当i=n时,不考虑放置在(i+1,j)的情况.
  6. 记录已经监视的格点数,(当前最优值减去当前已放置个数)*5如果小于未监视的格点数,则一定达不到比当前最优值更好的情况,剪去.
  7. 类似于(6),考虑更紧的情况,并非每个机器人都能独立监视5个格点,至少会有m/4+5的冗余,这个剪枝仅适用于i<n-1的情况,因为最后两行由于最优值和已放置个数非常接近,总是达不到这个值.这个剪枝策略参考自习题答案.

2. 记录已被监视的节点个数.

是否被监视使用一个int而不是bool数组spy,初始化时将网格周围一圈都设为1.每添加一个机器人,将它本身及上下左右的spy都+1,取消一个机器人时则-1.当添加后为1时,表示这个节点新被监视,当取消后为0时,表示这个节点新被取消监视.

代码

/* LittleFall : Hello! */
#include <bits/stdc++.h>
const int M = 32, go[5][2]={{0,0},{0,1},{0,-1},{1,0},{-1,0}};

int n,m;
int anx[M][M], ans;
int put[M][M], spy[M][M], tmp, spys = 0;
int lim1, lim2;

void search(int i,int j);
void puta(int x,int y,int i,int j)
{
	put[x][y] = 1;
	tmp++;
	for(int i=0; i<5; ++i)
		if(++spy[x + go[i][0]][y + go[i][1]]==1) spys++;

	search(i,j+1);

	put[x][y] = 0;
	tmp--;
	for(int i = 0; i < 5; ++i)
		if(--spy[x + go[i][0]][y + go[i][1]]==0) spys--;
}
void search(int i,int j)
{
	if(tmp>=ans) return;
	while(i<=n && spy[i][j]) //已放置的不再被搜索
	{
		++j;
		if(j>m)	++i, j=1;
	}
	if(i>n)					//更新答案
	{
		ans = tmp;
		memcpy(anx, put, sizeof(put));
		return;
	}

	int reach = spys + (ans-tmp)*5;
	if(reach <= lim1) return;
	if(i<n-1 && reach <= lim2) return;

	if((i==n&&j==m) || spy[i][j+1]==0) puta(i,j,i,j);
	if(i<n) puta(i+1,j,i,j);
	if(j<m && (spy[i][j+1]==0 || spy[i][j+2]==0)) puta(i,j+1,i,j);
}
int main(void)
{
	freopen("5-17.txt","r",stdin);
	scanf("%d%d",&n,&m);

	ans = n * m / 3 + 2;
	lim1 = n * m, lim2 = n * m + m / 4 + 5;

	for(int i=0;i<=n+1;++i)
		spy[i][0] = spy[i][m+1] = 1;
	for(int i=0;i<=m+1;++i)
		spy[0][i] = spy[n+1][i] = 1;
	search(1,1);

	printf("%d\n",ans );
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			printf("%d%c",anx[i][j],j==m?'\n':' ' );

    return 0;
}
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值