#牛客网 2018年牛客多校算法寒假训练营练习比赛(第四场)图论 + 地图搜索题解

A-石油采集

题目描述

随着海上运u输石油泄漏的问题,一个新的有利可图的行业正在诞生,那就是撇油行业。如今,在墨西哥湾漂浮的大量石油,吸引了许多商人的目光。这些商人们有一种特殊的飞机,可以一瓢略过整个海面20米乘10米这么大的长方形。(上下相邻或者左右相邻的格子,不能斜着来)当然,这要求一瓢撇过去的全部是油,如果一瓢里面有油有水的话,那就毫无意义了,资源完全无法利用。现在,商人想要知道,在这片区域中,他可以最多得到多少瓢油。

地图是一个N×N的网络,每个格子表示10m×10m的正方形区域,每个区域都被标示上了是油还是水

输入描述:

测试输入包含多条测试数据
测试数据的第一行给出了测试数据的数目T(T<75)
每个测试样例都用数字N(N<50)来表示地图区域的大小,接下来N行,每行都有N个字符,其中符号’.’表示海面、符号’#’表示油面。

输出描述:

输出格式如下“Case X: M”(X从1开始),M是商人可以最多得到的油量。

示例1

输入

 

1
6
......
.##...
......
.#..#.
.#..##
......

输出

 

Case 1: 3

题目大意 : 连续两个相邻的方格为1瓢的大小,当油被取走后,剩下的那两块就成了海面不可取,输出最多可以取多少瓢

思路 : 类似联通块问题, 设一个为奇数点一个为偶数点,每当碰到‘#’就进地图搜, 并且把搜过的地方置为‘.', 每次加上奇数点和偶数点的最小值的和即为答案。

AC代码 :

#include
using namespace std;
const int maxn = 1e2 + 5;

char p[maxn][maxn];
int n, m, odd, even, T, X;
int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};
void dfs(int x, int y, int step) {
    if (step & 1) even++;
    else odd++;
    for (int i = 0; i < 4; i++) {
        int xx = dir[i][0] + x;
        int yy = dir[i][1] + y;
        if (xx >= 0 && yy >= 0 && xx < n && yy < n && p[xx][yy] == '#') {
            p[xx][yy] = '.';
            dfs(xx, yy, step + 1);
        }
    }
}

int main()
{
    cin >> T;
    while (T--) {
        cin >> n;
        int ans = 0;
        for (int i = 0; i < n; i++) cin >> p[i];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (p[i][j] == '#') {
                    odd = even = 0;
                    p[i][j] = '.';
                    dfs (i, j, 0);
                    ans += min (odd, even);
                }
            }
        }
        cout << "Case " << ++X << ": ";
        cout << ans << endl;
    }
    return 0;
}

 

B-道路建设

 

题目描述

随着如今社会的不断变化,交通问题也变得越来越重要,所以市长决定建设一些公路来方便各个城市之间的贸易和交易。虽然市长的想法很好,但是他也遇到了一般人也经常头疼的问题,那就是手头的经费有限……在规划过程中,设计师们已经预算出部分城市之间建设公路的经费需求。现在市长想知道,它能不能将他的m个城市在有限的经费内实现公路交通。如果可以的话,输出Yes,否则输出No(两个城市不一定要直接的公路相连,间接公路到达也可以。)

输入描述:

测试输入包含多条测试数据
每个测试数据的第1行分别给出可用的经费c(<1000000),道路数目n(n<10000),以及城市数目m(<100)。
接下来的n行给出建立公路的成本信息,每行给出三个整数,分别是相连的两个城市v1、v2(0

输出描述:

对每个测试用例,输出Yes或No。

示例1

输入

 

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

输出

 

Yes

示例2

输入

 

10 2 2
1 2 5
1 2 15

输出

 

Yes

备注:

两个城市之间可能存在多条线路

 思路 : 裸的最小生成树,我用的是克鲁斯卡尔算法,每次找最短的边,看新加的两个点是否都没有加入集合,是就添加,否就跳过,最后再看下权值是否符合

AC代码 :

#include
using namespace std;
const int maxn = 1e4 + 5;

struct node
{
    int u, v, w;
}e[maxn];
bool cmp (node a, node b) {
    return a.w < b.w;
}
int p[maxn], n, m, c, ans, K;
void init() {
    memset(e, 0, sizeof(e));
    for (int i = 1; i <= n; i++) p[i] = i;
    ans = K = 0;
}
int find_(int x) {
    while (x != p[x]) x = p[x] = p[p[x]];
    return x;
}
void unite(int x, int y) {
    x = find_(x);
    y = find_(y);
    if (x != y) p[x] = y;
}

int main()
{
    while (cin >> c >> m >> n) {
        init();
        for (int i = 0; i < m; i++)
            cin >> e[i].u >> e[i].v >> e[i].w;
        sort (e, e + m, cmp);
        for (int i = 0; i < m; i++) {
            if (K == n - 1) break;
            if (find_(e[i].u) != find_(e[i].v)) {
                unite(e[i].u, e[i].v);
                ans += e[i].w;
                K++;
            }
        }
        if (K == n - 1 && ans <= c) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

 

 C-求交集

 

题目描述

给你两个升序排列的集合,求出两个集合的交集。

 

输入描述:

有多个测试用例,输入到文件结束。
对于每一个测试用例:
第一行输入两个整数n,m(0

输出描述:

每个测试用例用一行来输出两个集合的交集的所有元素(元素用空格隔开且按升序排列),若交集为空则输出"empty"。

示例1

输入

 

2 3
1 3
1 2 3

输出

 

1 3

备注:

交集为空的情况下,输出"empty"
。

题目大意 : 求两个集合的公共元素

思路 : 二分法依次查找就行,如果用lower_bound提交的话可能会TLE,直接手写就过了

AC代码 :

#include
using namespace std;
const int maxn = 1e6 + 5;

int p[maxn], s[maxn], n, m;

int main()
{
    while (~scanf("%d%d", &n, &m)) {
        bool flag = 0;
        for (int i = 0; i < n; i++) scanf("%d", &p[i]);
        for (int i = 0; i < m; i++) scanf("%d", &s[i]);
        for (int i = 0; i < n; i++) {
            int l = 0, r = m - 1, mid;
            while (l <= r) {
                mid = (l + r) >> 1;
                if (s[mid] > p[i]) r = mid - 1;
                else if (s[mid] < p[i]) l = mid + 1;
                if (s[mid] == p[i]) {flag = 1; break;}
            }
            if (flag) cout << p[i] << " ";
        }
        if (flag) cout << endl;
        else cout << "empty" << endl;
    }
    return 0;
}

 

D-小明的挖矿之旅

 

题目描述

    这个挖矿游戏会给出一个n*m个格子的地图,每个格子都有黄金。在游戏开始时小明会随机出现在地图的某一个格子当中。小明可以将他所在的格子的黄金收归囊中,并且还可以向下或者向右移动,然后继续收集黄金。地图上某些格子是障碍物,小明不能移动到有障碍物的格子上。不过,在游戏开始时,小明可以随意地在地图上放置传送门。传送门可以传送到地图上某一个确定的格子,传送门放置的位置和该传送门传送的位置在游戏开始之前必须放置和设置好传送地点。小明有无数个传送门可以使用,但是每次使用传送门所需要的游戏币也是很多的。小明想要至少要使用多少个传送门才能让他在游戏时无论出现在哪个格子,他都能拿到地图上的所有金子。

 

输入描述:

有多组测试数据。
对于每一组数据:
第一行输入两个整数n和m(0

输出描述:

输出一行。此行包括一个整数,表示小明所需传送门数量的最小值。

示例1

输入

 

3 3
...
.#.
...

输出

 

1

思路 : 题目的意思我没有理解,看别人的解释说是直接从第一个点搜,如果碰到障碍了就 + 1(QAQ真的不明白啊)

AC代码 :

#include
using namespace std;
char mp[1004][1004];
int main()
{
    int n, m, i, j;
    while(~scanf("%d%d", &m, &n))
    {
        int ans = 0, sum = 0;
        for(i = 0; i < m; i++) scanf("%s", mp[i]);
        for(i = 0; i < m ; i++)
            for(j = 0; j < n; j++)
            {
                if(mp[i][j] == '.')
                {
                    sum++;
                    if((mp[i+1][j] == '#' || i == m-1) && (mp[i][j+1] == '#' || j == n-1))
                        ans++;
                }
            }
        if(sum == 1) ans = 0;
        printf("%d\n", ans);
    }
    return 0;
}

 

E-通知小弟

 

题目描述

        在战争时期,A国派出了许多间谍到其他国家去收集情报。因为间谍需要隐秘自己的身份,所以他们之间只是单向联系。所以,某个间谍只能单向联系到一部分的间谍。同时,间谍也不知道跟他联系的是谁。
HA是间谍们的老大,但他也只能联系到部分的间谍。HA现在有一项命令有告诉所有的间谍。HA想要知道他至少要告诉多少个他能联系上的间谍才能通知到所有的间谍。

输入描述:

有多个测试数据。
对于每个测试数据:
第一行为一个整数n,m(0

输出描述:

输出一行,此行中有一个整数,代表HA至少需要联系的间谍数。如果HA不能通知到所有间谍,输出-1。

示例1

输入

 

3 2
1 2
1 2
1 1
0

输出

 

-1

示例2

输入

 

3 1
1
2 2 3
0
0

输出

 

1

 题目大意 :输入你能够通知的人, 输入一张有向图, U  V表示 U可以联系到V, 输出你最少需要通知多少人,才能够通知到所有人, 如果无法通知到所有人, 输出-1

思路 :先缩点, 在新图中找入度为0的点, 因为入度为0所以“需要被通知”, 看所有入度为0的点是否都在最开始输入的数里,如果有一个无法通知,则输出-1

AC代码 :

#include
using namespace std;
const int maxn = 1e6 + 5;

struct node
{
    int v, next;
}e[maxn];
int dfn[maxn], low[maxn], in[maxn], n, m, t, vi;
int head[maxn], suo[maxn], pre[maxn], cnt, tot, scnt;
bool vis[maxn];
stack  st;
map  mp;
void init() {
    memset(in, 0, sizeof(in));
    memset(vis, 0, sizeof(vis));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(suo, 0, sizeof(suo));
    memset(head, -1, sizeof(head));
    tot = scnt = cnt = 0;
}
void add (int from, int to) {
    e[++cnt].v = to;
    e[cnt].next = head[from];
    head[from] = cnt;
}
void tarjan(int x) {
    dfn[x] = low[x] = ++tot;
    st.push(x);
    vis[x] = 1;
    for (int i = head[x]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (!dfn[v]) {
            tarjan(v);
            low[x] = min (low[x], low[v]);
        }
        else if (vis[v]) low[x] = min (low[x], dfn[v]);
    }
    if (dfn[x] == low[x]) {
        scnt++;
        int k;
        do {
            k = st.top();
            st.pop();
            suo[k] = scnt;
            vis[k] = 0;
        }
        while (k != x);
    }
}

int main()
{
    while (cin >> n >> m) {
        init();
        for (int i = 0; i < m; i++) {
            cin >> t;
            mp[t]++;   // 表示能够通知
        }
        for (int i = 1; i <= n; i++) {
            cin >> t;
            while (t--) {
                cin >> vi;
                add (i, vi);   //存图
            }
        }
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) tarjan(i);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j != -1; j = e[j].next) {
                int u = suo[i], v = suo[e[j].v];
                if (u != v) in[v]++;   //新点更新入度
            } 
        }
        int ans = 0, flag = 1;
        for (int i = 1; i <= scnt; i++) {
            if (!in[i]) {
                bool ma = 0;
                for (auto it : mp) {
                    if (suo[it.first] == i) {ma = 1; break;}  
                }
                if (ma) ans++;  
                else {flag = 0; break;}   // 通知不到则输出-1
            }
        }
        if (!flag) cout << -1 << endl;
        else cout << ans << endl;
        mp.clear();   //注意初始化
    }
    return 0;
}

 

F-Call to your teacher

 

 

题目描述

从实验室出来后,你忽然发现你居然把自己的电脑落在了实验室里,但是实验室的老师已经把大门锁上了。更糟的是,你没有那个老师的电话号码。你开始给你知道的所有人打电话,询问他们有没有老师的电话,如果没有,他们也会问自己的同学来询问电话号码。那么,你能联系到老师并且拿到电脑吗。

输入描述:

存在多组测试样例
每组样例的第一行分别是两个整数n(1

输出描述:

对于每组测试样例,如果你最终能联系到老师,输出“Yes”,否则输出“No”。

示例1

输入

 

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

输出

 

Yes

示例2

输入

 

4 3
1 2
2 3
4 1

输出

 

No

题目大意 : 你的编号为1, 老师的编号为N, 输入一张有向图表示 U  能够通知  V, 问能否从1 到N

思路 : 直接dijkstra走一遍, 看1 到N 是否有路就行

AC代码 :

#include
using namespace std;
const int maxn = 1e4 + 5;
const int INF = 0x3f3f3f3f;

struct node
{
    int v, w, next;
}e[maxn];
struct edge
{
    int id, w;
    bool operator < (const edge &oth) const
    {
        return w > oth.w;
    }
}mid;
int head[maxn], dis[maxn], n, m, cnt;
priority_queue  q;
void init() {
    memset(e, 0, sizeof(e));
    memset(head, -1, sizeof(head));
    cnt = 0;
}
void add (int from, int to, int dis) {
    e[++cnt].v = to;
    e[cnt].w = dis;
    e[cnt].next = head[from];
    head[from] = cnt;
}
void dijkstra(int u) {
    memset(dis, INF, sizeof(dis));
    dis[u] = 0;
    q.push({u, 0});
    while (!q.empty()) {
        mid = q.top();
        q.pop();
        int ans = mid.id;
        if (mid.w != dis[ans]) continue;
        for (int i = head[ans]; i != -1; i = e[i].next) {
            if (dis[e[i].v] > dis[ans] + e[i].w) {
                dis[e[i].v] = dis[ans] + e[i].w;
                q.push({e[i].v, dis[e[i].v]});
            }
        }
    }
}

int main()
{
    while (cin >> n >> m) {
        init();
        for (int i = 0; i < m; i++) {
            int ui, vi;
            cin >> ui >> vi;
            add (ui, vi, 1);
        }
        dijkstra(1);
        if (dis[n] != INF) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

 G-老子的意大利炮呢

 

题目描述

自攻打过太原县城以后,李云龙这意大利炮就使用的越发的顺手了,指哪打哪也是绝不含糊,于是小野队长开始疯狂打击独立团炮兵营,为了锻炼士兵移动意大利炮的能力,李团长开始给炮兵营进行特训,众(wo)所(xia)周(bian)知(de),意大利炮可由炮管,车轮,炮弹三部分组成,现在李团长在洋村设立阵地,分别把炮管,车轮,炮弹放在三个不同的地方,每一部分的重量不同,所以运输的速度也不相同。团长说:你谁有能耐最快把这意大利炮组装好然后运到我面前,老子就赏他半斤地瓜烧,和尚听了乐坏了,但是他不知道怎么才能使时间最快,你能帮帮他么?

零件只要拿起,就不能放下,所以说不存在在a点拿起,b点放下再去取别的零件一说。默认和尚1秒走一个单位距离,并且只能向上下左右四个方向走,假设和尚现在拿着炮管,那么他每走一单位距离则需要(t1 + 1)秒,如果再拿上车轮,那么他现在每走一步则需要(t1 + t2+1)秒的时间,以此类推。点可以重复经过,路过有零件的点时,可以选择拿或者不拿。

输入描述:

第一行给定两个整数n, m代表地图大小,(1<=n, m <= 100);接下来n行每行有m个由‘#’和‘.’组成的字符,’#’代表墙,’.’代表路,和尚功夫高,可以自己或者带着零件(不论墙多厚),但是组装好意大利炮之后就必需得走路了。地图画好之后,接下来一行有10个整数sx,sy,x1, y1, x2, y2, x3, y3, ex, ed(都小于100)代表五个点,分别是起始点,炮管的位置,车轮的位置,炮弹的位置,李团长的位置(终点), 五个点保证不同。最后一行三个整数t1, t2, t3,(都大于等于1,小于100),分别代表炮管,车轮,炮弹每走一单位距离需要的时间。题目保证数据合法。

输出描述:

输出一个整数并换行,代表和尚完成任务所需要的最短时间。题目保证有解。

示例1

输入

 

3 5
##.##
.#.#.
##.##
1 3 2 1 2 3 2 5 3 3 
1 5 4

输出

 

34

说明

对于第一组样例,我们发现只有在2号零件也就是车轮的位置组装完才能运回到李团长的位置,所以最优解为:起始点->1号零件 –> 3号零件-> 2号零件->李团长
路线为(1,3),(2,3),(2,2),(2,1),(2,2),(2,3),(2,4),(2,5),(2,4),(2,3),(3,3)。
答案为34。

题目大意 : 有三个零件,每个零件有各自的重量,一个都不带的时候走一个单位要花一个单位的时间,装上零件后花的时间要加上该零件的重量,当三个零件都装在身上时不可,输出最短到达目的地的时间

思路 : 由于零件比较少,所以我用枚举 + BFS,把零件1, 零件2, 零件3之间用全排列表示,也就只有6个情况,然后加上一头一尾的距离就OK了

AC代码 :

#include
using namespace std;
const int maxn = 1e2 + 5;
const int INF = 0x3f3f3f3f;

struct node
{
    int x, y, step;
};
char p[maxn][maxn];
int n, m, x1, yy, x2, y2, x3, y3;
int sx, sy, ex, ey, t1, t2, t3;
int s1, s2, s3, s4, min_ = INF;
int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};
bool vis[maxn][maxn];
bool check (int x, int y, int v) {
    if (v != t1 + t2 + t3) {  
        if (x > 0 && y > 0 && x <= n && y <= m && !vis[x][y]) return true;
        else return false;
    }
    else {  //三个零件都带上就不能了
        if (x > 0 && y > 0 && x <= n && y <= m && !vis[x][y] && p[x][y] != '#') return true;
        else return false;
    }
}
int bfs(int x, int y, int px, int py, int val) {   //val是根据情况来分别判定条件的
    queue  q;
    q.push({x, y, 0});
    memset(vis, 0, sizeof(vis));
    while (!q.empty()) {
        node now = q.front();
        q.pop();
        if (now.x == px && now.y == py) return now.step;
        for (int i = 0; i < 4; i++) {
            int xx = now.x + dir[i][0];
            int yy = now.y + dir[i][1];
            if (check (xx, yy, val)) {
                vis[xx][yy] = 1;
                q.push({xx, yy, now.step + val + 1});
            }
        }
    }
    return INF;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        getchar();
        for (int j = 1; j <= m; j++) scanf("%c", &p[i][j]);
    }
    cin >> sx >> sy >> x1 >> yy >> x2 >> y2 >> x3 >> y3 >> ex >> ey;
    cin >> t1 >> t2 >> t3;
    s1 = bfs(sx, sy, x1, yy, 0);
    s2 = bfs(x1, yy, x2, y2, t1);
    s3 = bfs(x2, y2, x3, y3, t1 + t2);
    s4 = bfs(x3, y3, ex, ey, t1 + t2 + t3);  //以下就是枚举的情况,其实可以写的更好看些。。
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    s2 = bfs(x1, yy, x3, y3, t1);
    s3 = bfs(x3, y3, x2, y2, t1 + t3);
    s4 = bfs(x2, y2, ex, ey, t1 + t2 + t3);
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    s1 = bfs(sx, sy, x2, y2, 0);
    s2 = bfs(x2, y2, x1, yy, t2);
    s3 = bfs(x1, yy, x3, y3, t2 + t1);
    s4 = bfs(x3, y3, ex, ey, t1 + t2 + t3);
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    s2 = bfs(x2, y2, x3, y3, t2);
    s3 = bfs(x3, y3, x1, yy, t2 + t3);
    s4 = bfs(x1, yy, ex, ey, t1 + t2 + t3);
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    s1 = bfs(sx, sy, x3, y3, 0);
    s2 = bfs(x3, y3, x1, yy, t3);
    s3 = bfs(x1, yy, x2, y2, t1 + t3);
    s4 = bfs(x2, y2, ex, ey, t1 + t2 + t3);
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    s2 = bfs(x3, y3, x2, y2, t3);
    s3 = bfs(x2, y2, x1, yy, t3 + t2);
    s4 = bfs(x1, yy, ex, ey, t1 + t2 + t3);
    if (s1 != INF && s2 != INF && s3 != INF && s4 != INF) min_ = min (min_, s1 + s2 + s3 + s4);
    cout << min_ << endl;
    return 0;
}

 

H-老子的全排列呢

 

题目描述

老李见和尚赢了自己的酒,但是自己还舍不得,所以就耍起了赖皮,对和尚说,光武不行,再来点文的,你给我说出来1-8的全排序,我就让你喝,这次绝不耍你,你能帮帮和尚么?

输入描述:

输出描述:

1~8的全排列,按照全排列的顺序输出,每行结尾无空格。

示例1

输入

 

No_Input

输出

 

Full arrangement of 1~8

备注:

1~3的全排列  :
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

AC代码 :

#include
using namespace std;

int p[10] = {1, 2, 3, 4, 5, 6, 7, 8};

int main()
{
    do {
        for (int i = 0; i < 7; i++) cout << p[i] << " ";
        cout << p[7] << endl;
    }
    while (next_permutation (p, p + 8));
    return 0;
}

 

你可能感兴趣的:(牛客)