【集训】DFS/BFS专训3

DFS/BFS专训3


A. 生日蛋糕

P1731 [NOI1999] 洛谷传送门 一道涉及多重剪枝的经典dfs

题目描述
Mr.W 要制作一个体积为 N π Nπ Nπ M M M 层生日蛋糕,每层都是一个圆柱体。 设从下往上数第 i i i 蛋糕是半径为 R i R_i Ri,高度为 H i H_i Hi 的圆柱。当 i < M i<M i<M 时,要求 R i > R i + 1 R_i>R_i+_1 Ri>Ri+1 H i > H i + 1 H_i>H_i+_1 Hi>Hi+1。由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q Q Q 最小。令 Q = S π Q=Sπ Q=Sπ ,请编程对给出的 N N N M M M ,找出蛋糕的制作方案(适当的 R i R_i Ri H i H_i Hi 的值),使 S S S 最小。(除 Q Q Q 外,以上所有数据皆为正整数)

思路
"当 i < M i<M i<M 时,要求 R i > R i + 1 R_i>R_i+_1 Ri>Ri+1 H i > H i + 1 H_i>H_i+_1 Hi>Hi+1",我们可以根据半径和高度由上至下依次递增的方式,得出每一层最小的面积和体积,初始化数组 minv[ ] , minv[ ]. 后续剪枝依赖于此
三重剪枝:
剪枝1:当前表面积s大于目前最优解ans return
剪枝2:当前体积v大于题目给定n return
剪枝3:由最小体积推得的表面积2(n-v)/r+s大于目前最优解ans return

输入格式
第一行为 N N N ,表示待制作的蛋糕的体积为 N π Nπ Nπ
第二行为 M M M ,表示蛋糕的层数为 M M M

输出格式
输出仅一行,一个整数 S S S(若无解则 S = 0 S=0 S=0 )。

输入样例

100
2

输出样例

68

代码

#include <iostream>
#include <cstdio>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, mins[20007], minv[20007], ans = inf;

void dfs(int now, int r, int h, int s, int v) {
//    当前    层   半径   高度   表面积   体积
    int maxh = h;
    //搜索到最后一层 若体积v等于题目要求n 取最优ans
    if (now == 0) {
        if (v == n)
            ans = min(ans, s);
        return;
    }
    // 剪枝1:当前表面积s大于目前最优解ans
    if (s + mins[now - 1] >= ans)
        return;
    // 剪枝2:当前体积v大于题目给定n
    if (v + minv[now - 1] > n)
        return;
    // 剪枝3:由最小体积推得的表面积2(n-v)/r+s大于目前最优解ans 
    if (2 * (n - v) / r + s >= ans)
        return;
    // 由大到小枚举半径
    for (int i = r - 1; i >= now; i--) {
        // 最下面的一层 s由0初始化为底面积
        if (now == m)
            s = i * i;
        // 计算最大高度
        // 由 上一层-1 和 最大体积求出的h 取最小
        maxh = min(h - 1, (n - minv[now - 1] - v) / i / i);
        for (int j = maxh; j >= now; j--) {
        	// 层数-1  当前半径 当前高 当前表面积 当前体积
            dfs(now - 1, i, j, s + 2 * i * j, v + i * i * j);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    // 初始化每层最小的侧表面积和体积 逐级递增1
    for (int i = 1; i <= m; i++) {
        minv[i] = minv[i - 1] + i * i * i;
        mins[i] = mins[i - 1] + 2 * i * i; // S侧/π = 2*R
    }
    dfs(m, n, n, 0, 0);
    if (ans == inf)
        printf("0");
    else
        printf("%d", ans);
}

B. 埃及分数

题目描述
在这里插入图片描述
思路
csdn优秀题解 关于上下界的解释
有限迭代加深搜索 IDDFS
剪枝1:计算当前新分母 i 的上下界 枚举i 从下界枚举至上界
剪枝2:每次分出的分母要比上一次分出的分母大

通分细节:

x y − 1 i = x ∗ i − y y ∗ i \dfrac{x}{y}-\dfrac{1}{i} = \dfrac{x*i-y}{y*i} yxi1=yixiy
n x = x ∗ i − y nx=x*i-y nx=xiy
n y = y ∗ i ny=y*i ny=yi

输入格式

一行两个整数,分别为 a 和 b 的值。1 < a < b < 1000

输出格式
输出若干个数,自小到大排列,依次是单位分数的分母。

输入样例

19 45

输出样例

5 6 18

代码

#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;
ll a, b, c, anss;
ll tmp[10010], ans[10010];

ll gcd(ll x, ll y) { return y == 0 ? x : gcd(y, x % y); }
//下界
ll down(ll x, ll y) {
    for (int i = 2;; i++) 
        if (x * i > y)  return i;
}
//上界
bool up(ll k, ll x, ll y, ll i) {
    if (k * y > x * i)  return 1;
    return 0;
}
bool dfs(ll k, ll x, ll y) {
// 分到第k个数 当前 剩余分数x/y
    if (k == 1) {
    //最后的check条件: 剩余的分数x=1 且 y大于上一次的分母(剪枝1)
    //为第一次搜索结果 或 优于最优解(最后一个分母的值越小越好)
        if (x == 1 && y > tmp[c] && (anss == 0 || y < ans[anss])) {
            c++;
            anss = c;
            tmp[c] = y;
            for (int i = 1; i <= c; i++) ans[i] = tmp[i]; // ans数组转存
            c--;
            return 1;
        }
        return 0;
    }
    bool flag = 0;
    //枚举i从下界到上界
    for (ll i = max(down(x, y), tmp[c] + 1); up(k,x,y,i); i++) {
    //减掉1/i之后的新分数nx/ny
        ll nx = x * i - y;
        ll ny = y * i;
        ll g = gcd(nx, ny);// 取分子分母公因数 准备进行约分
        tmp[++c] = i; //存入答案暂存数组tmp
        if (dfs(k - 1, nx / g, ny / g))
            flag = 1;
        c--;
    }
    return flag;
}

bool judge() {
    int g = gcd(a, b);
    a /= g, b /= g;
    if (a == 1) {
        printf("%lld", b);
        return 1;
    } else
        return 0;
}

int main() {
    scanf("%lld%lld", &a, &b);
    if (judge())  return 0;//判断是否本身约分后为1/nx
    for (ll i = 2;; i++) {
        c = 0;
        if (dfs(i, a, b)) {
            for (int i = 1; i <= anss; i++) printf("%lld ", ans[i]);
            return 0;
        }
    }
}

C. 最优贸易

题目描述

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1~n,阿龙决定从 1 号城市出发,并最终在 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 个城市。

阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

27.png

假设 1 ~ n 号城市的水晶球价格分别为 4,3,5,6,1 。

阿龙可以选择如下一条线路:1->2->3->5,并在 2 号城市以 3 的价格买入水晶球,在 3 号城市以 5 的价格卖出水晶球,赚取的旅费数为 2 。

阿龙也可以选择如下一条线路 1->4->5->4->5,并在第 1 次到达 5 号城市时以 1 的价格买入水晶球,在第 2 次到达 4 号城市时以 6 的价格卖出水晶球,赚取的旅费数为 5 。

现在给出 n 个城市的水晶球价格, m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

输入格式
输入第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。

第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。

接下来 m 行,每行有 3 个正整数 x,y,z每两个整数之间用一个空格隔开。如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。

输出格式
输出共 1 行,包含 1 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0 。

输入样例

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

输出样例

5

代码
经典的分层图做法

#include <iostream>
#include <cstdio>
#include <queue>
#define inf 0x3f
#define maxn 110000
#define maxm 510000
using namespace std;

int n, m, w[maxn], head[maxn * 3], vis[maxn * 3], dis[maxn * 3];
int cnt, ans;

struct node {
    int now, dis;
    bool operator<(const node &x) const { return x.dis < dis; }
};
struct edge {
    int to, next, w;
} e[maxm * 3];
priority_queue<node> q;

void add(int u, int v, int w) {
    cnt++;
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void dj(int s) {
    for (int i = 1; i <= n * 3; i++) {
        dis[i] = inf;
    }
    node f = { s, 0 };
    q.push(f);
    dis[1] = 0;
    while (!q.empty()) {
        node tt = q.top();
        q.pop();
        int t = tt.now;
        for (int i = head[t]; i; i = e[i].next) {
            int v = e[i].to;
            if (dis[v] > dis[t] + e[i].w) {
                dis[v] = dis[t] + e[i].w;
                node vv = { v, dis[v] };
                q.push(vv);
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &w[i]);
    }
    for (int i = 1; i <= m; i++) {
        int u, v, t;
        scanf("%d%d%d", &u, &v, &t);
        for (int j = 0; j < 3; j++) {
            add(u + n * j, v + n * j, 0);
        }
        add(u, v + n, w[u]);
        add(u + n, v + 2 * n, -w[u]);
        if (t == 2) {
            for (int j = 0; j < 3; j++) {
                add(v + n * j, u + n * j, 0);
            }
            add(v, u + n, w[v]);
            add(v + n, u + 2 * n, -w[v]);
        }
    }
    dj(1);
    if (dis[n * 3] > 0)
        ans = 0;
    else
        ans = -dis[n * 3];
    printf("%d", ans);
}

这里有一种很取巧的dfs方法 来自洛谷某大佬 但我并没有理解

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;

int n,m,w[111000],a[111000],mi[111000],f[111000];
int maxn;

vector<int>q[111000];

void dfs(int k,int minn,int pre){
	bool flag=1;
	minn=min(minn,w[k]);
	if(mi[k]>minn) mi[k]=minn,flag=0;
	int maxn=max(f[pre],w[k]-minn);
	if(f[k]<maxn) f[k]=maxn,flag=0;
	if(flag) return ;
	for(int i=0;i<q[k].size();i++){
		dfs(q[k][i],minn,k);
	}
} 

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	for(int i=1;i<=n;i++)
		mi[i]=1e9;
	for(int i=1;i<=m;i++){
		int x,y,t;
		scanf("%d%d%d",&x,&y,&t);
		q[x].push_back(y);
		if(t==2) q[y].push_back(x);
	}
	dfs(1,1e9,0);
	printf("%d",f[n]);
}

D. 关系网络

题目描述
有 n 个人,他们的编号为 1~n,其中有一些人相互认识,现在 x 想要认识 y,可以通过他所认识的人来认识更多的人(如果 a 认识 b,b 认识 c,那么 a 可以通过 b 来认识 c),求出 x 最少需要通过多少人才能认识 y。

输入格式
第 1 行 3 个整数 n 、 x 、 y n、x、y nxy 2 ≤ n ≤ 100 2≤n≤100 2n100; 接下来的 n 行是一个 n × n n×n n×n 的邻接矩阵, a [ i ] [ j ] = 1 a[i][j]=1 a[i][j]=1 表示 i 认识 j, a [ i ] [ j ] = 0 a[i][j]=0 a[i][j]=0 表示不认识。 保证 i=j 时, a [ i ] [ j ] = 0 a[i][j]=0 a[i][j]=0,并且 a [ i ] [ j ] = a [ j ] [ i ] a[i][j]=a[j][i] a[i][j]=a[j][i]

输出格式
一行一个整数,表示 x 认识 y 最少需要通过的人数。数据保证 x 一定能认识 y。

输入样例

5 1 5
0 1 0 0 0
1 0 1 1 0
0 1 0 1 0
0 1 1 0 1
0 0 0 1 0

输出样例

2

代码
vector代替邻接表宽搜
比较简单 不解释

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;

int n, x, y, cnt;
bool v[150];
int ans[140];
vector<int> a[150];
queue<int> q;

void bfs() {
    while (!q.empty()) {
        int f = q.front();
        q.pop();
        for (int i = 0; i < a[f].size(); i++) {
            int t = a[f][i];    
            if (t == y) {
                cnt = ans[f] + 1;
                return;
            }
            if (v[t] == 0) {
                v[t] = 1;
                ans[t] = ans[f] + 1;
                q.push(t);
            }
        }
    }
}
int main() {
    scanf("%d%d%d", &n, &x, &y);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            int t;
            scanf("%d", &t);
            if (t == 1) {
                a[i].push_back(j);
            }
        }
    }
    q.push(x);
    v[x] = 1;
    ans[x] = 0;
    bfs();
    printf("%d", cnt - 1);
}

E. 四色地图

题目描述
地图四色定理(Four color theorem)最先是由一位叫古德里(Francis Guthrie)的英国大学生提出来的。其内容是:“任何一张地图只用四种颜色就能使具有共同边界的国家着上不同的颜色。”用数学语言表示,即“将平面任意地细分为不相重叠的区域,每一个区域总可以用1,2,3,4这四个数字之一来标记,而不会使相邻的两个区域得到相同的数字。”这里所指的相邻区域,是指有一整段边界是公共的。如果两个区域只相遇于一点或有限多点,就不叫相邻的。因为用相同的颜色给它们着色不会引起混淆。

如图所示,给出一任意地图,溃败的黑暗军团就散布在该地图的各个区域,墨老师一行四人需要带队搜索各区域,为了防止混乱,规定彼此搜索的区域互不相邻,请尝试如何分配。
在这里插入图片描述

输入格式
第一行为N(1<N≤26),表示区域数。随后N行描述各区域之间是否相邻。

输出格式
以1,2,3,4分别代表四人的编号(颜色),输出各区域的编号。

输入样例

4
1 2 3 
2 1 4
3 1 4
4 3 2

输出样例

1 2 2 1

代码
dfs 注意check

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n;
vector<int> map[50];
int a[50];
bool flag;
bool check(int x) {
    for (int i = 1; i < map[x].size(); i++) {
        int t = map[x][i];
        if (a[x] == a[t])
            return 0;
    }
    return 1;
}
void dfs(int x) {
    if (x > n) {
        if (flag == 1)
            return;
        for (int i = 1; i <= n; i++) {
            printf("%d ", a[i]);
        }
        printf("\n");
        flag = 1;
    } else {
        for (int i = 1; i <= 4; i++) {
            a[x] = i;
            if (check(x)) {
                // printf("%d %d\n",x,a[x]);
                dfs(x + 1);
            }
        }
    }
}

int main() {
    scanf("%d", &n);
    getchar();
    getchar();
    for (int i = 1; i <= n; i++) {
        string s;
        getline(cin, s);
        int t = 0;
        for (int j = 0; j < s.size(); j++) {
            if ((s[j] <= '9') && (s[j] >= '0')) {
                t *= 10;
                t += (s[j] - '0');
            } else
                t = 0;
            if (t != 0 && (s[j + 1] < '0' || s[j + 1] > '9'))
                map[i].push_back(t);
        }
    }
    dfs(1);
}

F. 小猫爬山

题目描述
Freda和rainbow饲养了N只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

Freda和rainbow只好花钱让它们坐索道下山。索道上的缆车最大承重量为W,而N只小猫的重量分别是C1、C2……CN。当然,每辆缆车上的小猫的重量之和不能超过W。每租用一辆缆车,Freda和rainbow就要付1美元,所以他们想知道,最少需要付多少美元才能把这N只小猫都运送下山?

输入格式
第一行包含两个用空格隔开的整数,N和W。

接下来N行每行一个整数,其中第i+1行的整数表示第i只小猫的重量Ci。

输出格式
输出一个整数,最少需要多少美元,也就是最少需要多少辆缆车。

输入样例

5 1996
1
2
1994
12
29

输出样例

2

数据范围对于100%的数据, 1 < = N < = 18 1<=N<=18 1<=N<=18 1 < = C i < = W < = 1 0 8 1<=Ci<=W<=10^8 1<=Ci<=W<=108

代码

#include <iostream>
#include <cstdio>
using namespace std;

int n, w, a[20], t[21], ans = 1e9;
bool v[20];

void dfs(int k, int cnt) {
    if (ans <= cnt)
        return;
    if (k == n + 1) {
        ans = min(ans, cnt);
    }
    for (int i = 1; i <= cnt; i++) {
    //如果能放的下的话将当前小猫放进每一个筐
        if (t[i] + a[k] <= w) {
            t[i] += a[k];
            dfs(k + 1, cnt);
            t[i] -= a[k];
        }
    }
    //新建筐
    t[cnt + 1] = a[k];
    dfs(k + 1, cnt + 1);
    t[cnt + 1] = 0;
}
int main() {
    scanf("%d%d", &n, &w);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    dfs(1, 0);
    printf("%d", ans);
}

G. 最大黑区域

题目描述
二值图像是由黑和白两种像素组成的矩形点阵,图像识别的一个操作是求出图像中最大黑区域的面积。请设计一个程序完成二值图像的这个操作。黑区域由黑像素组成,一个黑区域中的每像素至少与该区域中的另一像素相邻,规定一个像素仅与其上、下、左、右的像素相邻。两个不同的黑区域没有相邻的像素。一个黑区域的面积是其所包含的像素数。

输入格式
第 1 行含两个整数 n 和 m, 1 < = n , m < = 100 1<=n,m<=100 1<=n,m<=100,分别表示二值图像的行数与列数。后面n行,每行含m个整数0或1,其中第i行表示图像的第i行的m个像素,0表示白像素,1 表示黑像素。每行中的两个数之间用一个空格分隔。

输出格式
输出一行一个数,表示相应的图像中最大黑区域的面积。

输入样例

5 6
0 1 1 0 0 1
1 1 0 1 0 1
0 1 0 0 1 0
0 0 0 1 1 1
1 0 1 1 1 0

输出样例

7

代码

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;

struct node {
    int x, y;
};
int n, m;
int map[150][150];
bool v[150][150];
int dx[4] = { 0, 1, 0, -1 };
int dy[4] = { 1, 0, -1, 0 };
priority_queue<int> ans;
queue<node> q;

int bfs(int x, int y) {
    int cnt = 1;
    while (!q.empty()) {
        node f = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            node t;
            t.x = f.x + dx[i];
            t.y = f.y + dy[i];
            if (t.x <= n && t.x > 0 && t.y <= m && t.y > 0 
            && map[t.x][t.y] == 1 && v[t.x][t.y] == 0) {
                v[t.x][t.y] = 1;
                q.push(t);
                cnt++;
            }
        }
    }
    return cnt;
}

int main() {
    scanf("%d%d", &n, &m);
    printf("%d %d", n, m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", &map[i][j]);
            printf("%d ", map[i][j]);
        }
        printf("\n");
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (v[i][j] == 0 && map[i][j] == 1) {
                v[i][j] = 1;
                q.push({ i, j });
                int t = bfs(i, j);
                //				printf("%d\n",t);
                ans.push(t);
            }
        }
    }
    printf("%d", ans.top());
}

H. 引水入城

题目描述
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个 N 行 M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第 1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。

由于第 N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。
在这里插入图片描述

输入格式
输入的每行中两个数之间用一个空格隔开。
输入的第一行是两个正整数 N 和 M,表示矩形的规模;
接下来 N 行,每行 M 个正整数,依次代表每座城市的海拔高度。

输出格式
输出有两行。
如果能满足要求,输出的第一行是整数 1 ,第二行是一个整数,代表最少建造几个蓄水厂;
如果不能满足要求,输出的第一行是整数 0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。

输入样例1

2 5
9 1 5 4 3
8 7 6 1 2

输出样例1

1
1

输入样例1

3 6
8 4 5 6 4 4
7 3 4 3 3 3
3 2 2 1 1 2

输出样例1

1
3

代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

int n, m, cnt_0;
int a[600][600];
int l[600][600], r[600][600];
bool v[600][600];

int dx[4] = { 0, 1, 0, -1 };
int dy[4] = { 1, 0, -1, 0 };

void dfs(int x, int y) {
    v[x][y] = 1;
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if (nx < 1 || nx > n || ny < 1 || ny > m)
            continue;
        if (a[nx][ny] >= a[x][y])
            continue;
        if (!v[nx][ny])
            dfs(nx, ny);
        l[x][y] = min(l[x][y], l[nx][ny]);
        r[x][y] = max(r[x][y], r[nx][ny]);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", &a[i][j]);
        }
    }
    memset(l, 0x3f, sizeof(l));
    for (int i = 1; i <= m; i++) {
        r[n][i] = l[n][i] = i;
    }
    for (int i = 1; i <= m; i++) {
        if (!v[1][i]) {
            dfs(1, i);
        }
    }
    for (int i = 1; i <= m; i++) {
        if (!v[n][i])
            cnt_0++;
    }
    if (cnt_0) {
        printf("0\n%d", cnt_0);
        return 0;
    }
    int st = 1, mr = 0, cnt = 0;
    while (st <= m) {
        for (int i = 1; i <= m; i++) {
            if (st >= l[1][i])
                mr = max(mr, r[1][i]);
        }
        cnt++;
        st = mr + 1;
    }
    printf("1\n%d\n", cnt);
}

I. 武士风度的牛

题目描述
农民John有很多牛,他想交易其中一头被Don称为The Knight的牛。

这头牛有一个独一无二的超能力,在农场里像Knight一样地跳(就是我们熟悉的象棋中马的走法)。

虽然这头神奇的牛不能跳到树上和石头上,但是它可以在牧场上随意跳,我们把牧场用一个x,y的坐标图来表示。

这头神奇的牛像其它牛一样喜欢吃草,给你一张地图,上面标注了The Knight的开始位置,树、灌木、石头以及其它障碍的位置,除此之外还有一捆草。

现在你的任务是,确定The Knight要想吃到草,至少需要跳多少次。

The Knight的位置用’K’来标记,障碍的位置用’*’来标记,草的位置用’H’来标记。

这里有一个地图的例子:

             11 | . . . . . . . . . .
             10 | . . . . * . . . . . 
              9 | . . . . . . . . . . 
              8 | . . . * . * . . . . 
              7 | . . . . . . . * . . 
              6 | . . * . . * . . . H 
              5 | * . . . . . . . . . 
              4 | . . . * . . . * . . 
              3 | . K . . . . . . . . 
              2 | . . . * . . . . . * 
              1 | . . * . . . . * . . 
              0 ----------------------
                                    
                0 1 2 3 4 5 6 7 8 9 10 

The Knight 可以按照下图中的A,B,C,D…这条路径用5次跳到草的地方(有可能其它路线的长度也是5):

             11 | . . . . . . . . . .
             10 | . . . . * . . . . .
              9 | . . . . . . . . . .
              8 | . . . * . * . . . .
              7 | . . . . . . . * . .
              6 | . . * . . * . . . F<
              5 | * . B . . . . . . .
              4 | . . . * C . . * E .
              3 | .>A . . . . D . . .
              2 | . . . * . . . . . *
              1 | . . * . . . . * . .
              0 ----------------------
                                    
                0 1 2 3 4 5 6 7 8 9 10

输入格式
第1行: 两个数,表示农场的列数C(C<=150)和行数R(R<=150)。

第2…R+1行: 每行一个由C个字符组成的字符串,共同描绘出牧场地图。

输出格式
一个整数,表示跳跃的最小次数。

输入样例

10 11
..........
....*.....
..........
...*.*....
.......*..
..*..*...H
*.........
...*...*..
.K........
...*.....*
..*....*..

输出样例

5

代码
经典bfs

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int m, n, a[1000][1000];
bool vis[1000][1000];
int dx[8] = { -1, -2, 2, 1, 1, 2, -1, -2 };
int dy[8] = { 2, 1, 1, 2, -2, -1, -2, -1 };
string s;
struct node {
    int x, y, dis;
};
node ed, st;

bool edge(int x, int y) {
    if (x < 1 || y < 1 || x > n || y > m)
        return 0;
    return 1;
}
queue<node> q;
void bfs() {
    while (!q.empty()) {
        node f = q.front();
        q.pop();
        if (f.x == ed.x && f.y == ed.y) {
            printf("%d", f.dis);
        }
        for (int i = 0; i < 8; i++) {
            int nx = f.x + dx[i];
            int ny = f.y + dy[i];
            if (!edge(nx, ny))
                continue;
            if (a[nx][ny] == 0)
                continue;
            if (vis[nx][ny] == 1)
                continue;
            vis[nx][ny] = 1;
            node t = { nx, ny, f.dis + 1 };
            q.push(t);
        }
    }
}

int main() {
    scanf("%d%d\n", &m, &n);
    for (int i = 1; i <= n; i++) {
        getline(cin, s);
        for (int j = 0; j < m; j++) {
            if (s[j] == '.')
                a[i][j + 1] = 1;
            if (s[j] == '*')
                a[i][j + 1] = 0;
            if (s[j] == 'H')
                a[i][j + 1] = 1, ed.x = i, ed.y = j + 1;
            if (s[j] == 'K')
                a[i][j + 1] = 1, st.x = i, st.y = j + 1;
        }
    }
    st.dis = 0;
    q.push(st);
    vis[st.x][st.y] = 1;
    bfs();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值