20150301题解

本文介绍了四道算法题目:山峰和山谷(BFS解决连通块问题)、超级马(使用BFS判断能否到达所有区域)、传递游戏(DFS求最小代价总和)和皇后守卫(利用二进制优化进行搜索)。通过这些题目,阐述了BFS和DFS在解决实际问题中的应用和优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

山峰和山谷(grz)
Description
Byteasar特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。为了能够让他对他的旅程有一个安排,他想知道山峰和山谷的数量。
给定一个地图,为Byteasar想要旅行的区域,地图被分为n*n的网格,每个格子(i,j) 的高度w(i,j)是给定的。
若两个格子有公共顶点,那么他们就是相邻的格子。(所以与(i,j)相邻的格子有(i−1, j−1),(i−1,j), (i−1,j+1), (i,j−1), (i,j+1), (i+1,j−1), (i+1,j), (i+1,j+1))。
我们定义一个格子的集合S为山峰(山谷)当且仅当:
1.S的所有格子都有相同的高度。
2.S的所有格子都联通
3.对于s属于S,与s相邻的s’不属于S。都有ws>ws’(山峰),或者ws小于ws’(山谷)
你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。
Input Format
输入文件grz.in第一行包含一个正整数n,表示地图的大小(1<=n<=1000)。接下来一个n*n的矩阵,表示地图上每个格子的高度。(0<=w<=1000000000)
Output Format
输出文件grz.out应包含两个数,分别表示山峰和山谷的数量。
Sample Input 1
5
8 8 8 7 7
7 7 8 8 7
7 7 7 7 7
7 8 8 7 8
7 8 8 8 8
Sample Output 2
2 1
Sample Input 2
5
5 7 8 3 1
5 5 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 0 1 7
Sample Output 2
3 3
Solution
一道经典的bfs问题,遍历整个图求各个高度相同的连通块,若在扩展中遇到了高度比当前连通块小的,则当前连通块不是山谷;若在扩展中遇到了高度比当前连通块大的,则当前连通块不是山峰。
Code

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int n, map[1001][1001];
bool vis[1001][1001];
int hans, lans;//山峰、山谷 
int dx[9] = {0, 1, 1, 1, 0, -1, -1, -1, 0}, dy[9] = {0, -1, 0, 1, 1, 1, 0, -1, -1};//增量数组 
struct pnt {
    int x, y, h;        //坐标,高度 
    pnt(int x = 0, int y = 0, int h = 0) 
        :x(x), y(y), h(h) {}//构造器 
};
inline void bfs(int x, int y) {
    queue <pnt> q;
    q.push(pnt(x, y, map[x][y]));
    pnt u;
    bool hgh = true, lw = true;//记录是否为山峰、山谷 
    while(!q.empty()) {
        u = q.front();
        q.pop();
        for(int i = 1; i <= 8; ++i) {
            u.x += dx[i]; u.y += dy[i];
            if(u.x>0 && u.x<=n && u.y>0 && u.y<=n) {
                if(map[u.x][u.y] > u.h) hgh = false;
                else if(map[u.x][u.y] < u.h) lw = false;
                else if(!vis[u.x][u.y]) {
                    vis[u.x][u.y] = true;
                    q.push(pnt(u.x, u.y, map[u.x][u.y]));
                }
            }
            u.x -= dx[i]; u.y -= dy[i];
        }
    }
    if(hgh) ++hans;
    if(lw) ++lans;
}
int main() {
    freopen("grz.in", "r", stdin);
    freopen("grz.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
        scanf("%d", &map[i][j]);
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
        if(!vis[i][j]) bfs(i, j);
    printf("%d %d", hans, lans);
    return 0;
}

超级马(sup)
Description
在一个无限的棋盘上有一个超级马,它可以完成各种动作。每一种动作都是通过两个整数来确定——第一个数说明列的数(正数向右,负数向左),第二个数说明行的数(正数向上,负数向下),移动马来完成这个动作。
编写一个程序,从文本文件SUP.IN输入说明各种超级马的数据库。
对每一个超级马进行确认,是否通过自己的行动可以到达盘面上的每一个区。
将结果存储到文本文件SUP.OUT。
Input Format
在文本文件SUP.IN 的第一行中存在一个整数k,它代表数据库的数1≤k≤100。在这个数字后出现K 数据库。它们的每一个第一行中会出现整数N,它是马能够完成的各种动作的数,1≤n≤100。接下来数据库的每一个行中包含两个整数P 和Q,它们由单个空格分开,说明动作,-100≤p,q≤100。
Output Format
文本文件SUP.OUT 应由K 行组成,当第i 个数据库的超级马可以到达棋盘面的每一个区,第i 行应包含一个词TAK,而另一个词NIE 则恰恰相反。
Sample Input
2
3
1 0
0 1
-2 -1
5
3 4
-3 -6
2 -2
5 6
-1 4
Sample Output
TAK
NIE
Solution
因为坐标一次不超过[-100,100],棋盘开到201*201就可以。再bfs,从棋盘中心开始,记录是否中心的上下左右都可以走到,若可以则能走到整个棋盘。注意去重。一开始写成是否能走到201*201的所有格子了,又慢又容易错。
Code

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int sumpoints = 40401;
int T, n;
int dx[101], dy[101];
bool vis[201][201];
struct pnt {
    int x, y;
    pnt(int x = 100, int y = 100) :x(x), y(y) {}
};
bool bfs() {
    queue <pnt> q;
    q.push(pnt());
    vis[100][100] = true;
    pnt u;
    while(!q.empty()) {
        u = q.front();
        q.pop();
        for(int i = 1; i <= n; ++i) {
            u.x += dx[i]; u.y += dy[i];
            if(u.x>=0 && u.x<=200 && u.y>=0 && u.y<=200)
                if(!vis[u.x][u.y]) {
                    q.push(u);
                    vis[u.x][u.y] = true;
                    if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100])
                        return true;
                }
            u.x -= dx[i]; u.y -= dy[i];
        }
    }
    if(vis[100][99] && vis[100][101] && vis[99][100] && vis[101][100]) return true;
    else return false;
}
int main() {
    freopen("sup.in", "r", stdin);
    freopen("sup.out", "w", stdout);
    scanf("%d", &T);
    while(T--) {
        memset(dx, 0, sizeof(dx));
        memset(dy, 0, sizeof(dy));
        memset(vis, false, sizeof(vis));
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d%d", &dx[i], &dy[i]);
        if(bfs()) puts("TAK");
        else puts("NIE");
    }
    return 0;
}

传递游戏(pass)
Description
n个人在做传递物品的游戏,编号为1-n。
游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。
即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;
求当物品经过所有n个人后,整个过程的最小总代价是多少。
Input Format
第一行为n,表示共有n个人(16>=n>=2);
以下为n*n的矩阵,第i+1行、第j列表示物品从编号为i的人传递到编号为j的人所花费的代价,特别的有第i+1行、第i列为-1(因为物品不能自己传给自己),其他数据均为正整数(<=10000)。
Output Format
一个数,为最小的代价总和。
Sample Input
2
-1 9794
2724 –1
Sample Output
2724
Solution
dfs:
传3个参数,一个表示当前传到的人,一个是传过了多少人,一个是当前代价;
最优化剪枝:若当前代价加上(所有人中最小的代价*(n-传过的人数))>=ans则剪掉。可以1s出解,但一开始我写挫了,记录的是每个人的最小代价,又用了奇怪的卡时,WA了。
Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<climits>
using namespace std;
int n, map[17][17], ans = INT_MAX;
int times, maxm;
bool vis[17];
void dfs(int k, int lft, int now) {
    if(lft == n) {
        ans = min(ans, now);
        return;
    }
    if(now+(n-lft)*maxm >= ans) return;
    for(int i = n; i >= 1; --i) if(!vis[i]) {
        vis[i] = true;
        dfs(i, lft+1, now + map[k][i]);
        vis[i] = false;
    }
}
int main() {
    freopen("pass.in", "r", stdin);
    freopen("pass.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j){
        scanf("%d", &map[i][j]);
        if(i != j) maxm = min(maxm, map[i][j]);
    }
    for(int i = n; i >= 1; --i) {
        memset(vis, false, sizeof(vis));
        vis[i] = true;
        dfs(i, 1, 0);
    }
    printf("%d", ans);
    return 0;
}
dp:
方程:f[i][k] = min(f[i][k], f[j][k^pos[i-1]]+map[j][i]);
i,j表示由j传到了i,k是一个二进制数表示状态,1为当前传过了某位置,0则为未传过,详见代码。

Code

#include<cstdio>
#include<algorithm>
#include<climits>
#include<cstring>
using namespace std;
int pos[17] = {1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15, 1<<16};
//二进制记录状态,1表示该位置的人已经传过,0表示没有传过 
int n, map[17][17];
int f[17][70000], ans = 707406378;
inline void read(int &a) {//读入优化 
    int f = 1;
    char s = getchar();
    while(s<'0' || s>'9')   {
        if(s == '-') f = -1;
        s = getchar();
    }
    while(s>='0' && s<='9') {
        a = a*10+s-48;
        s = getchar();
    }
    a *= f;

}
inline void write(int a) {//输出优化 
    char s[20];
    int top = 0;
    if(a < 0) {
        putchar('-');
        a = -a;
    }
    do {
        s[++top] = a%10+48;
        a /= 10;
    } while(a);
    for(int i = top; i >= 1; --i) putchar(s[i]);
}
inline void init() {//读入数据 
    freopen("pass.in", "r", stdin);
    read(n);
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
        read(map[i][j]);
}
inline void print() {//输出数据 
    freopen("pass.out", "w", stdout);
    write(ans);
}
inline void work() {//dp 
    memset(f, 127/3, sizeof(f));
    for(int i = 1; i <= n; ++i) f[i][pos[i-1]] = 0; 
    for(int k = 0; k <= pos[n]-1; ++k)  //枚举已经传过了哪些人 
        for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) {//当前由j传到了i 
            if(i == j) continue;
            if((k&pos[i-1]) != pos[i-1]) continue;//若不等,则说明没有传过i,不符 
            if((k&pos[j-1]) != pos[j-1]) continue;//若不等,则说明没有传过j,不符 
            f[i][k] = min(f[i][k], f[j][k^pos[i-1]]+map[j][i]);//k^pos[i-1]表示的状态就是pos[i-1]并把i的位置去掉 
        }
    for(int i = 1; i <= n; ++i) ans = min(f[i][pos[n]-1], ans);
}
int main() {
    init();
    work();
    print();
    return 0;
}

皇后守卫(queen)
Description
给一个N * M的棋盘,棋盘上的有些格子被打上了标记。现在需要在其中放置尽量少的皇后,使得所有被打上标记的格子至少被某一个皇后攻击或占据到。皇后之间可以互相攻击。
Input Format
输入最多15组数据。
每组数据第一行包含两个整数N和M(1 < N, M < 10),以下为一个N行M列的棋盘,其中打上标记的格子用‘X’表示,其它格子用‘.’表示。
输入以一个0结尾。
Output Format
对于每组数据,输出一个数表示最少需要使用的皇后数目。
Sample Input
8 8
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
XXXXXXXX
8 8
X…….
.X……
..X…..
…X….
….X…
…..X..
……X.
…….X
0
Sample Output
(注意输出格式为每组数据占一行,Case k:m表示第k组数据对应的需要使用的皇后的数目)
Case 1: 5
Case 2: 1
Solution
本题可以先写暴搜,发现答案最大是5,于是答案的上界就定下来了。具体的搜法可以是枚举皇后的位置,如果在某位置可以控制某些X,就递归这个位置。本题最优化剪枝是很明显的,但若只有这些的话还是会T。于是乎我就加了个错误的剪枝:只在X上放皇后,然后就全W了。
还有一个剪枝是从X出发寻找可以控制X的格子,不过我写的方法比较挫,T了。
最终设法参透了洪一大神的二进制优化,本质上是另一种搜法:
使用一个二进制数k表示每一行被皇后控制的状态,一个一维数组就可以记录整个棋盘。
二进制的一些知识:
一个数左移后一定是偶数,偶数(or)1等同于偶数+1;
……
以下来自

http://blog.youkuaiyun.com/qq_21110267/article/details/44002315


/*
许多精巧的位运算 :
判断是否满足条件, 标记的地方都被覆盖到 枚举 x, 如果所有 (k[x]&map[x]) == map[x] 说明满足 解释 : map[x]表示第 x 行需要被覆盖的二进制状态. 只有当 map[x] 为 1 的地方k[x] 也为 1 才满足
更新状态, 在第 x 行二进制下 p 位置放置皇后对原可覆盖状态 k 的影响
*/
end;
dfs传递4个参数,当前递归的格子横纵坐标,棋盘状态,当前已用的皇后数。
若标记的格子均已被覆盖,更新答案,返回;
若当前已用的皇后数加一(此时不满足条件,否则在第一步就已经返回)大于等于答案则返回;
递归在这个格子放皇后的情况;
递归不放皇后的情况;
Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20;
const int maxans = 5;
int n, m, T, ans, maxlim;
int map[maxn];
inline void read(int &a) {
    a = 0;
    char ch = getchar();
    int f = 1;
    while(ch<'0' || ch>'9') {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(ch>='0' && ch<='9') {
        a = a*10+ch-48;
        ch = getchar();
    }
}
inline void write(int a) {
    char c[20];
    int top = 0;
    if(a < 0) {
        putchar('-');
        a = -a;
    }
    do {
        c[++top] = a%10+48;
        a /= 10;
    } while(a);
    for(int i = top; i >= 1; --i) putchar(c[i]);
} 
inline void print(int x, int y) {
    putchar('C'); putchar('a'); putchar('s');
    putchar('e'); putchar(' '); write(x);
    putchar(':'); putchar(' '); write(y);
    putchar('\n');
}
//以上一坨为IO优化
void update(int x, int y, int *k) {//放皇后以后更新棋盘状态 
    k[x] = maxlim;
    for(int i = 1; x-i >= 0; ++i) {
        if((y<<i) < maxlim) k[x-i] |= (y<<i);
        if((y>>i) > 0) k[x-i] |= (y>>i);
        k[x-i] |= y;
    }
    for(int i = 1; x+i < n; ++i) {
        if((y<<i) < maxlim) k[x+i] |= (y<<i);
        if((y>>i) > 0) k[x+i] |= (y>>i);
        k[x+i] |= y;
    }
}
bool check(int *k) {            //检查是否满足条件 
    for(int i = 0; i < n; ++i)
        if((k[i]&map[i]) != map[i]) return false;
    return true;
}
void dfs(int x, int y, int *k, int now) {
    if(check(k)) {
        ans = min(ans, now);
        return;
    }
    if(y >= m) { ++x; y = 0; }
    if(x>=n || now+1>=ans) return;
    int p = (1<<y);
    int k2[maxn];
    memcpy(k2, k, sizeof(int)*n);
    update(x, p, k2);
    dfs(x, y+1, k2, now+1);
    dfs(x, y+1, k, now);
}
int main() {
    freopen("queen.in", "r", stdin);
    freopen("queen.out", "w", stdout);
    char ch[20];
    while(++T) {
        read(n);
        if(n == 0) break;
        read(m);
        maxlim = (1<<m)-1;                  //当前棋盘一行的格子数 
        memset(map, 0, sizeof(map));
        for(int i = 0; i < n; ++i) {
            gets(ch);
            for(int j = 0; j < m; ++j) {    //预处理棋盘 
                map[i]<<=1;
                if(ch[j] == 'X') map[i] |= 1;
            }
        }
        int k[maxn];
        ans = maxans;
        memset(k, 0, sizeof(k));
        dfs(0, 0, k, 0);
        print(T, ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值