题解.洛谷题单之搜索

题1.八皇后:

P1219 [USACO1.5]八皇后 Checker Challenge - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

n*n的棋盘,即dfs有n层,每层有n个分支;

dfs传入当前在哪一行这一个参数,每次查找当前行能用的棋子再继续递归向下;

为控制每列,每条对角线上只有一个棋子,给定两个bool数组记录每列和每个对角线上是否有棋子。

图示:

代码如下:

#include<iostream>
using namespace std;
const int N = 15;
bool st[N];
int dx[2 * N], dy[2 * N],n,path[N],res=0;
void dfs(int u) {
	if (u == n) {
		res++;
		if (res < 4) {
			for (int i = 0; i < n; i++)
				cout << path[i] << " ";
			cout << endl;
		}
		
		return;
	}
	for (int i = 0; i < n; i++) {
		if (!st[i]&&!dx[u+i]&&!dy[u-i+N]) {
			st[i] = true;
			dx[u + i] = true;
			dy[u - i + N] = true;
			path[u] = i+1;
			dfs(u + 1);
			st[i] = false;
			dx[u + i] = false;
			dy[u - i + N] = false;
		}
	}
}
int main() {
	cin >> n;
	dfs(0);
	cout<<res;
	return 0;
}

题2.kkksc03考前临时抱佛脚:

P2392 kkksc03考前临时抱佛脚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

每个科目最多20题,二进制枚举所有情况,时间复杂度O(2^20),一共四科,总体O(2^22)。

代码如下:

#include<iostream>
#include<cmath>
using namespace std;
const int N = 25,INF=1e9;
int q[N], res = 0,cur=INF;
int main() {
	int a[4];
	int l, r;
	for (int i = 0; i < 4; i++)
		cin >> a[i];
	for (int i = 0; i < 4; i++) {
		cur = INF;
		for (int u = 0; u < a[i]; u++)
			cin >> q[u];
		for (int j = 0; j < pow(2, a[i]); j++) {
			l = r = 0;
			for (int k = 0; k < a[i]; k++) {
				if (j >>k& 1)l += q[k];
				else r += q[k];
			}
			int st = max(l, r);
			cur = min(cur, st);
		}
		res += cur;
	}
	cout << res;
	return 0;
}

题3.马的遍历:

P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

dfs或者bfs均可。

代码如下:

#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
const int N = 410;
int res[N][N];
int a, b, n, m;
using namespace std;
typedef pair<int, int>PII;
int  bfs(PII start) {
	queue<PII>p;
	p.push(start);
	int dx[] = { -2,-2,-1,-1,1,1,2,2 };
	int dy[] = { 1,-1,2,-2,2,-2,1,-1 };
	while (p.size()) {
		PII t = p.front();
		p.pop();
		for (int i = 0; i < 8; i++) {
			int x = dx[i] + t.x, y = dy[i] + t.y;
			if (x<1 || x>n || y<1 || y>m)continue;
			if (res[x][y] != -1)continue;
			res[x][y] = res[t.x][t.y] + 1;
			p.push({ x, y });
		}
	}
	return -1;
}
int main() {
	cin >> n >> m >> a >> b;
	PII start = { a,b };
	memset(res, -1, sizeof(res));
	res[a][b] = 0;
	bfs(start);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			printf("%-5d", res[i][j]);
		cout << endl;
	}
}

题4.奇怪的电梯:

P1135 奇怪的电梯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

bfs最小步数模型问题;

将楼层数看做点,按一次按钮看做一条连接两个点的边,求起点到终点的最短路。

代码如下:

#include<iostream>
using namespace std;
const int N=210;
int qq[N],hh=0,tt=-1;
int q[N];
bool st[N];
int d[N];
int main(){
    int n,a,b;
    cin>>n>>a>>b;
    for(int i=1;i<=n;i++)cin>>q[i];
    qq[++tt]=a;
    st[a]=true;
    while(hh<=tt){
        int t=qq[hh++];
        if(t+q[t]<=n&&!st[t+q[t]]){qq[++tt]=t+q[t];st[t+q[t]]=true;d[t+q[t]]=d[t]+1;}
        if(t-q[t]>=1&&!st[t-q[t]]){qq[++tt]=t-q[t];st[t-q[t]]=true;d[t-q[t]]=d[t]+1;}
        if(t+q[t]<=n&&t+q[t]==b){cout<<d[t+q[t]];return 0;}
        if(t-q[t]>=1&&t-q[t]==b){cout<<d[t-q[t]];return 0;}
    }
    cout<<"-1";
    return 0;
}

题5.Meteor showers S:

P2895 [USACO08FEB]Meteor Shower S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

预处理处所有不安全点变成焦土的时间,再对起点进行bfs即可。

代码如下:

#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;

const int N = 310;
int t[N][N], g[N][N];//贝茜到达的时间和流星雨到达的时间
bool st[N][N];
int n;//n个流星
int dx[] = { 1,0,-1,0 ,0}, dy[] = { 0,1,0,-1 ,0};

int bfs() {
	if (g[0][0] == -1)return 0;
	queue<PII>q;
	q.push({ 0,0 });
	while (q.size()) {
		auto p = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			int a = p.x + dx[i],b=p.y + dy[i];
			int u = t[p.x][p.y] + 1;
			if (g[a][b]!=-1&&u >= g[a][b])continue;//流星到达后到
			if (a < 0||b<0)continue;//越界
			if (st[a][b])continue;

			st[a][b] = true;
			t[a][b] = t[p.x][p.y] + 1;
			q.push({ a,b });
			if (g[a][b] == -1)return t[a][b];
		}
	}
	return -1;
}

int main() {
	memset(g, -1, sizeof t);//g为-1时该点没有流星
	cin >> n;
	int a, b, c;
	while (n--) {//读入流星
		cin >> a >> b >> c;
		for (int i = 0; i < 5; i++) {
			if (a + dx[i] < 0 ||b + dy[i] < 0)continue;
			if (g[a+dx[i]][b+dy[i]] != -1)g[a + dx[i]][b + dy[i]] = min(g[a + dx[i]][b + dy[i]], c);
			else g[a + dx[i]][b + dy[i]] = c;
		}
		
	}

	cout << bfs();
	return 0;
}

题6.选数:

P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

试除法判断是否为质数;

数据范围为n小于等于20,二进制枚举和dfs均可,给出dfs代码。

代码如下:

#include<iostream>
using namespace std;
const int N = 25;
bool st[N];//状态数组
int a[N];
int n, k, res = 0;

bool is_prime(int x) {
	for (int i = 2; i <= x / i; i++)
		if (x % i == 0)return false;
	return true;
}

void dfs(int u) {
	if (u == n) {//递归结束
		int cnt=0,tmp=0;
		for (int i = 0; i < n; i++)
			if (st[i]) {
				cnt++; tmp += a[i];
			}
		if (cnt == k && is_prime(tmp))res++;//如果选了k个数并且和为素数
		return;//回溯
	}

	st[u] = true;//选
	dfs(u + 1);
	st[u] = false;//不选
	dfs(u + 1);
}

int main() {
	cin >> n >> k;
	for (int i = 0; i < n; i++)cin >> a[i];
	dfs(0);
	cout << res;
	return 0;
}

题7.PERKET:

P2036 [COCI2008-2009#2] PERKET - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

数据范围为n小于等于10,同样使用dfs或者二进制枚举均可 ,给出dfs代码。

代码如下:

#include<iostream>
#include<algorithm>
#define x first
#define y second
using namespace std;
const int N = 15;
bool st[N];
pair<int, int> p[N];
int n,res=0x3f3f3f3f;

void dfs(int u) {
	if (u == n) {
		int a=1, b=0,cnt=0;
		for(int i=0;i<n;i++)
			if (st[i]) { cnt++; a *= p[i].x, b += p[i].y; }
		if(cnt)res = min(res, abs(a - b));
		return;
	}

	st[u] = true;
	dfs(u + 1);
	st[u] = false;
	dfs(u + 1);
}

int main() {
	cin >> n;
	int a, b;
	for (int i = 0; i < n; i++) {
		cin >> a >> b;
		p[i] = { a,b };
	}
	dfs(0);
	cout << res;
	return 0;
}

题8.吃奶酪:

P1433 吃奶酪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

暴力枚举奶酪的所有排列(若当前距离已经大于找到的最短距离,则剪掉当前的这枝);

数据范围为n于小等于15,不剪枝时间复杂度为O(n!);

洛谷题解很多用的状态压缩dp,不会,暴力法最后TLE3个点。

代码如下:

#include<iostream>
#include<vector>
#include<cmath>
#define x first
#define y second 
using namespace std;
typedef pair<double,double> PII;
const int N=20;
const int INF=0x3f3f3f; 
bool st[N];
double res=INF;
int n;
vector<PII>v;

//s到e的距离 
double get(PII s,PII e){
	double a=(double)s.x-(double)e.x;
	double b=(double)s.y-(double)e.y;
	return sqrt(a*a+b*b);
}

//层数、当前的路径长度 、当前点的坐标 
void dfs(int u,double cnt,PII s){
	if(cnt>=res)return;
	if(u==n){res=cnt;return;}
	
	for(int i=0;i<n;i++){
		if(!st[i]){
			double tmp=get(s,v[i]);//距离 
			st[i]=true;
			cnt+=tmp;
			dfs(u+1,cnt,v[i]);
			st[i]=false;
			cnt-=tmp;
		}
	}
	return;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		double a,b;
		cin>>a>>b;
		v.push_back({a,b});
	}
	dfs(0,0,{0,0}); 
	
	printf("%.2lf",res);
	return 0;
} 

题9.迷宫:

P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

每个方格最多走一次,求路径方所有案数之和;

从起点开始做一次完整的做dfs,遇到终点则方案数加一,并且回溯到上一层。

代码如下:

#include<iostream>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;

PII S, E;//起点和终点
const int N = 15;
int g[N][N];//迷宫
int st[N][N];//每个点的状态
int sx, sy, ex, ey;//起点和终点
int n, m, k;//n*m,k组数据
int res = 0;//走法
int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 };

void dfs(int u, PII s) {
	if (s == E) {//如果走到了
		res++;
		return;
	}
	//cout << s.x << " " << s.y << endl;

	for (int i = 0; i < 4; i++) {
		int a = s.x + dx[i], b = s.y + dy[i];
		if (a <=0 || a > n || b <= 0 || b > m)continue;//越界
		if (g[a][b] == 1)continue;//有障碍
		if (st[a][b])continue;//走过了
		st[s.x][s.y] = true;//选该点
		dfs(u + 1, { a,b });//下一层
		st[a][b] = false;//恢复操作
	}

}

int main() {
	cin >> n >> m >> k >> sx >> sy >> ex >> ey;
	S = { sx,sy }, E = { ex,ey };
	int a, b;
	while (k--) {//读入障碍
		cin >> a >> b;
		g[a][b] = 1;
	}
	
	dfs(0, S);//dfs,0层,从s开始搜

	cout << res;
	return 0;
}

题10.单词接龙:

P1019 [NOIP2000 提高组] 单词接龙 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

dfs求全排列即可;

数据范围为n小于等于20,但每个单词可以用两次,最坏时间复杂度为O(40!)(若数据给20个abbbba就TLE了);

在dfs之前,先预处理出所有单词两两重叠的长度。

代码如下:

#include<iostream>
using namespace std;
const int N=25;
string str[N];
int g[N][N];
int st[N];
int n;
int res=0;

void dfs(int a,int ans){
    res=max(res,ans);
    st[a]++;
    //cout<<"第"<<a<<"个字符串 长度为"<<ans<<" "<<st[a]<<endl;
    for(int i=1;i<=n;i++){
        if(st[i]==2)continue;
        if(!g[a][i])continue;
        dfs(i,ans+str[i].size()-g[a][i]);
        st[i]--;
    }
    
}

int main(){
    cin>>n;
    
    for(int i=1;i<=n;i++)
        cin>>str[i];
    char start;
    cin>>start;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            int a=str[i].size(),b=str[j].size();
            for(int k=1;k<a&&k<b;k++)
                if(str[i].substr(a-k,k)==str[j].substr(0,k)){g[i][j]=k;break;}
        }
        
    for(int i=1;i<=n;i++)
        if(str[i][0]==start)dfs(i,str[i].size());
        
    cout<<res;
    return 0;
}

题11.单词方阵:

P1101 单词方阵 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

枚举所有单词,当找到y时,分别朝八个方向偏移,如果找到目标字符串,则全部记录下来即可。

代码如下:

#include<iostream>
#include<cstring>
using namespace std;
int dx[]={-1,-1,0,1,1,1,0,-1};
int dy[]={0,1,1,1,0,-1,-1,-1};
const int N=110;
char g[N][N],res[N][N];
string str="yizhong";
int n;

int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			cin>>g[i][j];
			res[i][j]='*';
		}
		
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
		
			if(g[i][j]=='y')
				for(int k=0;k<8;k++){//往k的方向偏移 
				
					if(i+dx[k]*6>=n||i+dx[k]*6<0||j+dy[k]*6>=n||j+dy[k]*6<0)continue;//越界
					
					//判断k[i]方向是否合法 
					bool flag=true;
					for(int p=1;p<=6;p++){//往前走6步
						int x=i+dx[k]*p,y=j+dy[k]*p;//当前坐标 
						char tmp=str[p];//当前应该是的字符串
						if(g[x][y]!='*'&&g[x][y]!=tmp){flag=false;break;} 
					} 
					
					//若合法 
					if(flag){
						res[i][j]='y';
						for(int p=1;p<=6;p++){//往前走6步
							int x=i+dx[k]*p,y=j+dy[k]*p;//当前坐标 
							res[x][y]=str[p];
						} 
					}
						
				}
	
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)cout<<res[i][j];
		cout<<endl;
	}
	return 0;
		
}

题12.自然数拆分问题:

P2404 自然数的拆分问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

数据范围n小于等于8,dfs每层选一个数即可;

由于需要按字典序输出,所以dfs时,下一层选大于等于上一层的数,由于1 1 2和1 2 1是一样的,这样做也可以规避掉重复情况;

代码如下:

#include<iostream>
using namespace std;
const int N = 10;
int path[N];//路径
int n;

void dfs(int u,int s,int k) {//第n层,还差s,上一层选的k

	for (int i = k; i <= s; i++) {//寻找这一层选哪个数
		s -= i;//选i
		path[u] = i;//记录该层选了i
		if (s == 0&&u >= 1) {//如果大于等于两个数并且刚好拆完就是输出并且回溯
			for (int j = 0; j < u; j++)cout << path[j] << "+";
			cout << path[u] << endl;
			return;
		}
		dfs(u + 1, s, i);//继续拆
		s += i;//恢复操作
	}
}

int main() {
	cin >> n;
	dfs(0,n,1);
	return 0;
}

题13.Lake Counting S:

P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

bfs求连通块的数量。

代码如下:

#include<iostream>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;//坐标
const int N = 1010;
bool st[N][N];//状态数组
char g[N][N];//图
int n, m;

void bfs(PII s) {
	//起点入队
	queue<PII>q;
	q.push(s);
	int dx[] = { 0,1,0,-1 ,-1,1,1,-1},dy[] = { 1,0,-1,0,1,1,-1,-1};//偏移量,注意该题的偏移量有8个
	while (q.size()) {//队列不空
		//取出队头
		auto t = q.front();
		q.pop();
		for (int i = 0; i < 8; i++) {//遍历该点的上下左右
			int a = dx[i] + t.x, b = dy[i] + t.y;
			if (a < 0 || a >= n || b<0 || b>=m)continue;//越界
			if (st[a][b])continue;//搜过了
			if (g[a][b] == '.')continue;//该点位置不是陆地
			//如果该点符合要求
			st[a][b] = true;//标记
			q.push({ a,b });//入队
		}
	}
	return;
}

int main() {
	cin >> n >> m;
	for (int i = 0; i < n; i++)//读入图
		for (int j = 0; j < m; j++)cin >> g[i][j];

	int res = 0;
	for(int i = 0; i < n; i++)//遍历图
		for (int j = 0; j < m; j++)
			//如果遍历到的点没搜过并且是陆地
			if (!st[i][j] && g[i][j] == 'W') { res++; bfs({ i,j }); }
	cout << res;
	return 0;
}

题14.填涂颜色

P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

由题意,闭合圈由数字1构成,即0连通块被1包围需要被染色;

bfs或者dfs,如果搜到该连通块贴着边界则不染色,给出bfs代码。

代码如下:

#include<iostream>
#include<cstring>
#include<queue>
#define x first 
#define y second
using namespace std;

typedef pair<int, int>PII;
const int N = 35;
int g[N][N];
bool st[N][N];
int n;
int dx[] = { 1,0,- 1,0 }, dy[] = { 0,1,0,-1 };

bool bfs(PII s, int k) {
    memset(st, false, sizeof st);
	queue<PII>q;
	q.push(s);
	st[s.x][s.y] = true;
	g[s.x][s.y] = k;//染色

	while (q.size()) {
		auto t = q.front();
		q.pop();

		for (int i = 0; i < 4; i++) {
			int a = t.x + dx[i], b = t.y + dy[i];
			if (a < 0 || a >= n || b < 0 || b >= n) return false;//不是闭合圈
			if (g[a][b] == 1) continue;//如果是墙
			if (st[a][b])continue;//选过了
			
			//入队并且染色
			q.push({ a,b });
			st[a][b] = true;
			g[a][b] = k;
		}
	}
	return true;
}

int main() {
	cin >> n;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> g[i][j];

	bool res = false;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) {
			if (g[i][j] != 1) {
				res = bfs({ i,j }, 0);//如果不是墙,就判断是否为闭合圈
				if (res) { //如果是闭合圈就染成2
					bfs({ i,j }, 2); 
					for (int i = 0; i < n; i++) {
						for (int j = 0; j < n; j++)cout << g[i][j]<<" ";
						cout << endl;
					}
					return 0; 
				}
			}

		}

	
	
	return 0;
}

题15.字串变换:

P1032 [NOIP2002 提高组] 字串变换 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

双向bfs:双向BFS_双向广搜_如何何何的博客-CSDN博客

题16.Corn Maze S

P1825 [USACO11OPEN]Corn Maze S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:

迷宫问题加了一个传送操作;

bfs时特判该点是否是传送点即可,如果A,B两点是传送门,假设走到了A点,此时相当于走到了B点(将B点加入bfs队列),但是应该标记A点走过了但B点还没走过。

代码如下:

#include<iostream>
#include<queue>
#include<unordered_map>
//define x,y,方便取出pair里的坐标
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;//坐标
const int N = 310,M=30;
char g[N][N];//迷宫
bool st[N][N];//状态数组,表示该点搜过没有
int d[N][N];//距离起点的距离
int n, m;
//传送点的坐标 
PII L[M][2]; 

int bfs(PII s) {
	queue<PII>q;//定义队列
	
	q.push(s);//将起点入队
	st[s.x][s.y]=true;
	
	int dx[] = { 0,1,0,-1 }, dy[] = { 1,0,-1,0 };//偏移量
	
	while (q.size()) {//队列不空
		//取出队头
		auto t = q.front();
		q.pop();
		
		for (int i = 0; i < 4; i++) {
			int a = dx[i] + t.x,b = dy[i] + t.y;
			
			if (g[a][b] == '#')continue;//如果是走不通
			if (st[a][b])continue;//如果搜过了
			if (a < 0 || a >= n || b < 0 || b >= m)continue;//如果越界了
			
			
			//搜到(a,b)
			st[a][b] = true;
			
			if(g[a][b]=='.'){d[a][b]=d[t.x][t.y] + 1;q.push({ a,b });}//草地 
			
			else if(g[a][b]=='=')return d[t.x][t.y]+1;//终点 
			
			else if(g[a][b]>='A'&&g[a][b]<='Z'){//传送门 
			
				//传送门的两个坐标 
				int xx1=L[g[a][b]-'A'][0].x , yy1=L[g[a][b]-'A'][0].y;
				int xx2=L[g[a][b]-'A'][1].x , yy2=L[g[a][b]-'A'][1].y;
				//在xx1 yy1点 
				if(a==xx1&&b==yy1){q.push({xx2,yy2});d[xx2][yy2]=d[t.x][t.y]+1;}
				//在xx2 yy2点
				else{q.push({xx1,yy1});d[xx1][yy1]=d[t.x][t.y]+1;}
			}
		}
	}
	
	return -1;
}

int main() {
	for(int i=0;i<M;i++)
		L[i][0]=L[i][1]={-1,-1};//初始无传送门 
	
	cin >> n >> m;
	PII start,end;
	for (int i = 0; i < n; i++)//读入迷宫
		for (int j = 0; j < m; j++){
			cin >> g[i][j];
			
			//传送门 
			if(g[i][j]>='A'&&g[i][j]<='Z'){
				int k=g[i][j]-'A';
				if(L[k][0].x==-1)L[k][0]={i,j};
				else L[k][1]={i,j};	
			}
			else if(g[i][j]=='@')start={i,j};
		}
	
	cout<<bfs(start);//bfs
	return 0;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值