练习题(搜索,剪枝)

第一题:数独题:

 

判断重复

数独要求每一行、每一列、每一个3×3方阵内的数字,不重复。

行和列重复判断是相当简单的。我们可以定义两个bool型二维数组,当此行(或列)填充数字时,我们可以直接把这行的这个数字打上true表示有数字了。

//譬如第一行第三列填入数字2
bool p[][],l[][];//p:行,l:列;
p[1][2]=l[3][2]=true;
方阵序号=(行数-1)/3*3+(列数-1)/3+1
//注意!行数列数要-1,因为3的整数倍数/3会比原方阵大1,不能满足上述需求。
//stone_juice P1784 数独 
#include <bits/stdc++.h>//华丽的开头~ 
using namespace std;
int sd[11][11];//数独方阵定义 
bool p[11][11],l[11][11],fz[11][11];//行(排?),列,方阵。 
void _out()//优美地输出~ 
{
	for(int i=1;i<=9;i++)
	{	
  		for(int j=1;j<=9;j++)
			cout<<sd[i][j]<<" ";
		cout<<endl;
	}
	exit(0);//注意,此处要用exit(0)。用return的话不会退出dfs函数,会增加运算量。 
}
void dfs(int x,int y)//神奇的深搜~
{
	if(sd[x][y]!=0)//如果原来这个位置有数字,跳过。 
		if(x==9&&y==9)_out();//当行列都为9,填充完成,输出~
		else if(y==9)dfs(x+1,1);//当列数为9,搜索下一排。 
		else dfs(x,y+1);//搜下一列啦~ 
	else//原来的地方没有数字,准备填充! 
		for(int i=1;i<=9;i++)
			if((!p[x][i])&&(!l[y][i])&&(!fz[(x-1)/3*3+(y-1)/3+1][i]))
			//判断是不是重复了。方法题解有讲! 
			{
				sd[x][y]=i;//填充! 
				p[x][i]=l[y][i]=fz[(x-1)/3*3+(y-1)/3+1][i]=true;//打上标记。 
				if(x==9&&y==9)_out();//全部填完!输出~ 
				else if(y==9)dfs(x+1,1);//同上!搜下一行。
				else dfs(x,y+1);//搜下一列! 
				sd[x][y]=0; //恢复标记。 
				p[x][i]=l[y][i]=fz[(x-1)/3*3+(y-1)/3+1][i]=false;//恢复标记。 
			}
}
int main()
{
	for(int i=1;i<=9;i++)
		for(int j=1;j<=9;j++)
		{
			int t;//定义tmp(防止下面代码太长?) 
			cin>>t;//炫酷地输入 
			if(t!=0)
				p[i][t]=l[j][t]=fz[(i-1)/3*3+(j-1)/3+1][t]=true;
			//填充的不是0的话,表示原来有数字了。打上标记。	
			sd[i][j]=t;//填充进数独。 
		}	
	dfs(1,1);//搜搜搜! 
	return 0;//完美地结束~ 
}

 

第二题:研究骑士问题,就是从一个点是否能够到达另一个点

 你的一个朋友正在研究旅行骑士问题 (TKP),你将在其中找到最短的骑士移动封闭路径,该路径恰好访问棋盘上给定 n 个方格中的每个方格一次。他认为问题中最困难的部分是确定两个给定方格之间的最小骑士移动数,一旦完成了这一点,找到巡回赛将很容易。
你当然知道反之亦然。所以你让他写一个解决“困难”部分的程序。
你的工作是编写一个程序,它以两个正方形 a 和 b 作为输入,然后确定从 a 到 b 的最短路线上的骑士移动次数。
原文链接:https://blog.youkuaiyun.com/Hundreds_N/article/details/124227877

题解:

 本题采用广搜,而不是深搜,广搜的步数是在一个点后全部更新加一,可以做到同步。深度搜素需要从一个节点不断地深入搜索,直到搜到合适的位置,需要不断地判断界限和释放资源,已便于回溯的时候重新找路径。

#include<bits/stdc++.h>
using namespace std;

int st_x, st_y, ed_x, ed_y;
char s1[3], s2[3];  // 注意字符串数组最后还要存一个\0,所以多一个位置
int vis[9][9], r[8][2] = {{1, 2}, {2, 1}, {-2, 1}, {-1, 2}, {1, -2}, {2, -1}, {-2, -1}, {-1, -2}};

struct node{
	int x, y, step;
};

bool check(int x, int y) {  // 可移动到的位置,是否合法
	if(x >= 1 && x <= 8 && y >= 1 && y <= 8)
		return true;
	return false;
}

void bfs() {
	node cur, next;
	queue<node> q;
	cur.x = st_x, cur.y = st_y, cur.step = 0;  // 初始化
	vis[cur.x][cur.y] = 1;  // 标记
	q.push(cur);
	while(!q.empty()) {
		cur = q.front();
		q.pop();
		if(cur.x == ed_x && cur.y == ed_y) {  
			// 注意输出中 还有起点和终点的位置
			printf("To get from %s to %s takes %d knight moves.\n", s1, s2, cur.step);
			return;
		}
		for(int i = 0; i < 8; i++) {
			next.x = cur.x + r[i][0];
			next.y = cur.y + r[i][1];
			// 注意判断移动后的位置是否合法
			if(!check(next.x, next.y))	continue;
			if(!vis[next.x][next.y]) {
				next.step = cur.step + 1;
				vis[next.x][next.y] = 1;
				q.push(next);
			}
		}
	}
}

int main() {
	while(~scanf("%s %s", s1, s2)) {
		memset(vis, 0, sizeof(vis));
		// 将字符全部转为数字坐标
		st_x = s1[0] - 'a' + 1;
		st_y = s1[1] - '0';
		ed_x = s2[0] - 'a' + 1;
		ed_y = s2[1] - '0';
		//printf("%d %d %d %d\n", st_x, st_y, ed_x, ed_y);
		bfs();
	}
	return 0;
}

本题使用可以用 bfs 求解,然而直接 bfs 跑不过。能否优化?

观察发现每次移动空格只能和一个数交换位置,所以移动步数至少是每个数字的位置到目标状态位置的曼哈顿距离之和 ( 曼哈顿距离就是横坐标的差的绝对值与纵坐标的差的绝对值之和 )。

这样我们就可以用 A* 求解,设当前状态 now,目标状态 end。

可以设计估价函数 f(x) 横坐标的差的绝对值与纵坐标的差的绝对值之和。

然而若当前状态无解,再搜索会浪费很多时间,所以我们在使用 A* 求解时,需要先判断初始状态有没有解。

把状态从上到下,从左到右转换为一个序列,

例如:

1 2 3
4 5 6 => 1 2 3 4 5 6 7 8 空
7 8 空

观察发现每一次移动空格,都不会改变序列中逆序对数的奇偶性 ( 上下移动增加或减少两个,左右移动无影响 )。所以只有当前状态的逆序对数为偶数才有解。

最终的算法是先对初始状态进行归并排序求逆序对,判断可解性,再开始 A* 搜索,用 map/unordered_map 记录是否扩展过当前状态。

其实就是八数码问题

#include <bits/stdc++.h>
using namespace std;
int T,mp[10],zero; //mp存转换后的序列便于归并排序
string a; //用字符串存状态
void init() { //输入函数
    a="";
    char s[10];
    for(int i=1;i<=9;++i) {
        scanf("%s",s);
        a+=s[0];
        if(s[0]=='x') {
            mp[i]=0; //0代表空格
            zero=i-1; //记录空格位置
        } else {
            mp[i]=s[0]-'0';
        }
    }
}
int tp[10],sum;
void pai(int l,int r) { //归并排序
    if(l==r) return;
    int mid=(l+r)>>1;
    pai(l,mid);
    pai(mid+1,r);
    int i=l,j=mid+1,k=l-1;
    while(i<=mid&&j<=r) {
        if(mp[i]<mp[j]) {
            tp[++k]=mp[i++];
        } else {
            sum+=mid-i+1;
            tp[++k]=mp[j++];
        }
    }
    while(i<=mid) {
        tp[++k]=mp[i++];
    }
    while(j<=r) {
        tp[++k]=mp[j++];
    }
    for(int i=l;i<=r;++i) {
        mp[i]=tp[i];
    }
}
bool check() {
    sum=-zero; //逆序对数不包括含0的逆序对
    pai(1,9);
    if(sum%2) return 0;
    return 1;
}
int mbs(int x) {return x<0?-x:x;}
int f(string x) { //估价函数
    int ans=0;
    for(int i=0;i<9;++i) {
        if(x[i]!='x') {
            int d=x[i]-'0';
            ans+=mbs((d-1)/3-i/3)+mbs((d-1)%3-i%3); //i/3,(d-1)/3是行数,i%3,(d-1)%3是列数
        }
    }
    return ans;
}
struct node {
    string now,sol; //now存当前状态,sol存当前决策
    int dat; //dat=当前步数+估计步数
};
bool operator <(const node x,const node y) {
    return x.dat>y.dat;
}
priority_queue<node> q;
node make_node(string p,string s,int d) {
    node tmp;
    tmp.now=p,tmp.sol=s,tmp.dat=d;
    return tmp;
}
unordered_map<string,bool> ha;
string ed="12345678x"; //目标状态
int dx[4]={-3,3,-1,1}; //方向
string ds="udlr"; //方向对应操作符号
void bfs() {
    if(!check()) {
        puts("unsolvable");
        return;
    }
    while(!q.empty()) {
        q.pop();
    }
    q.push(make_node(a,"",f(a)));
    ha.clear();
    while(!q.empty()) {
        node tmp=q.top();
        q.pop();
        if(tmp.now==ed) {
            cout<<tmp.sol<<endl;
            break;
        }
        string now=tmp.now,sol=tmp.sol;
        if(ha.find(now)!=ha.end()) {
            continue;
        }
        int len=sol.length(); //当前状态已经走过的步数
        ha[now]=1;
        for(int i=0;i<9;++i) {
            if(now[i]!='x') continue;
            string t;
            for(int j=0;j<4;++j) { //枚举4个方向
                int p=i+dx[j],c=i%3+dx[j]; //p判断上下移动是否越界,c判断左右移动是否越界
                if(p<0||p>8) continue;
                if(j>1&&(c<0||c>2)) continue;
                t=now;
                swap(t[i],t[p]);
                if(ha.find(t)==ha.end()) {
                    q.push(make_node(t,sol+ds[j],len+1+f(t)));
                } //扩展状态
            }
        }
    }
}
int main() {
    scanf("%d",&T);
    while(T--) {
        init();
        bfs();
        if(T) putchar('\n');
    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值