搜索 POJ 2870 Light Up

时间:2022-09-10 19:55:22

题目链接:  POJ 2870 Light Up

分析:

求最小步数,首先想到了IDA*,但是写了1个多小时,严重超时。仔细思考后发现,步数可以达到20步以上,比如以下这个样例需要25步:

7 7
24
1 2 -1
1 4 -1
1 6 -1
2 1 -1
2 3 -1
2 5 -1
2 7 -1
3 2 -1
3 4 -1
3 6 -1
4 1 -1
4 3 -1
4 5 -1
4 7 -1
5 2 -1
5 4 -1
5 6 -1
6 1 -1
6 3 -1
6 5 -1
6 7 -1
7 2 -1
7 4 -1
7 6 -1
所以在用IDA*之前,还是要想一想步数会不会太多。

谈一谈朴素的深搜——

地图类型的搜索(例如数独) 一般都是采用一格一格的搜索,有时可以改变顺序(例如数独很多时候需要从条件最多的格子入手)。

如果按顺序搜索会有一个剪枝的方便之处,本题就用到了——后面的行极有可能无法影响前面的格子。


本题代码如下:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>

#define xx first
#define yy second
#define LL long long
#define CLEAR(xxx) memset(xxx,0,sizeof(xxx))
#define INTpair pair<int,int>

using namespace std;
const int maxn=20,inf=1e9;

int n,m,B,s[maxn][maxn],lit[maxn][maxn];
bool lamp[maxn][maxn];
int ans;
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
vector<INTpair> v;

void Change(int x,int y,int type){
	//点亮(type==1)或者 熄灭(type==-1)这个点 
	int i;
	lamp[x][y]= (type==1);
	for(i=y;i>0&&s[x][i]==-2;i--)lit[x][i]+=type;
	for(i=y+1;i<=m&&s[x][i]==-2;i++)lit[x][i]+=type;
	for(i=x-1;i>0&&s[i][y]==-2;i--)lit[i][y]+=type;
	for(i=x+1;i<=n&&s[i][y]==-2;i++)lit[i][y]+=type;
}

bool check1(){  //检查带编号的墙 ,是否满足要求 
	int i,j;
	for(i=0;i<v.size();i++){
		int x=v[i].xx,y=v[i].yy,cnt=0;
		for(j=0;j<4;j++)
			if(lamp[x+dx[j]][y+dy[j]])cnt++;
		if(cnt!=s[x][y]) return false;
	}
	return true;
} 

bool check2(){  
	//剪枝1: 检查带编号的墙 ,是否已经有超过要求的,有返回false 
	int i,j;
	for(i=0;i<v.size();i++){
		int x=v[i].xx,y=v[i].yy,cnt=0;
		for(j=0;j<4;j++)
			if(lamp[x+dx[j]][y+dy[j]])cnt++;
		if(cnt>s[x][y]) return false;
	}
	return true;
} 

bool check3(int x,int y){  
	//检查(x,y)这个点会不会和已经有的灯冲突 
	int i;
	for(i=y-1;i>0&&s[x][i]==-2;i--)if(lamp[x][i])return false;
	for(i=x-1;i>0&&s[i][y]==-2;i--)if(lamp[i][y])return false;
	return true;
}

bool ALL(){  //检查是否全部点亮 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(s[i][j]==-2&&!lit[i][j])return false;
	return true;
}

bool Above(int x,int y){
	/*  强剪枝: 
		检查墙(x,y) ,如果他的正上方还有没点亮的,那之后也没法点亮 
	*/ 
	for(int i=1;i<x;i++)
		if(s[i][y]==-2&&!lit[i][y]) return true;
	return false;
}

void PrintMap(){
	cout<<"*********&&&&&&&&&&************"<<endl;
	for(int i=1;i<=n;i++,cout<<endl)
		for(int j=1;j<=m;j++)cout<<lamp[i][j]<<" ";
	cout<<"*********&&&&&&&&&&************"<<endl;
}

void DFS(int x,int y,int dep){ //当前讨论(x,y)这个点,已经放了dep个灯 
	//cout<<x<<" "<<y<<" "<<dep<<endl;
	if(dep>=ans) return ;
	if(!check2()) return ;
	if(y==1&&x==n+1&&check1()&&ALL()){
		ans=min(ans,dep);
		//PrintMap();
		return ;
	}
	if(x>n||y>m) return ;
	if(s[x][y]>=-1){    //搜到墙了 
		if(x>1&&s[x-1][y]==-2&& Above(x,y))return;
		if(y<m) DFS(x,y+1,dep);
		else DFS(x+1,1,dep);
	}
	else {
		if(y<m)DFS(x,y+1,dep);
		else DFS(x+1,1,dep);
		if(check3(x,y)){
			Change(x,y,1);
			if(y<m)DFS(x,y+1,dep+1);
			else DFS(x+1,1,dep+1);
			Change(x,y,-1);
		}
	}
}

int main(){
	int i,j,x,y;
	while(cin>>n>>m){
		if(!n&&!m) return 0;
		CLEAR(s);CLEAR(lit); CLEAR(lamp);
		for(i=1;i<=n;i++)
			for(j=1;j<=m;j++)s[i][j]=-2;
		cin>>B;
		v.clear();
		for(i=1;i<=B;i++){
			cin>>x>>y;
			cin>>s[x][y];
			if(s[x][y]!=-1)v.push_back(make_pair(x,y));
		}
		ans=inf;
		DFS(1,1,0);
		if(ans==inf)cout<<"No solution"<<endl;
		else cout<<ans<<endl; 
	}
	return 0;
}