您的位置:首页 > 其它

NowCoder Winter Camp Contest IV

2018-02-14 22:38 393 查看
比赛链接:https://www.nowcoder.com/acm/contest/76#question

比赛时间:18.2.11 14:00 ~ 17:00

比赛内容:图论相关基础题目,包括bfs与dfs,最小生成树、最短路Dijstra、拓扑排序、强连通分量tarJan等图论相关的基础算法。适合图论新手。

A 石油采集

题目描述

给你一个NxN的海洋网格,每一格都由.和#组成,.代表海面,#代表油面,假设有一架飞机一次可以采横着的两格油或者竖着的两个油(相邻,现在问你最多能采多少次。

思路

暴力搜索法。在每一次采取一次后,就将这两个格子标志为1,然后再次搜索全海面。维护一个全局的最大值。但这种方法在油面比较多的时候时间复杂度时灾难的,比如全部都是油面,那么你每一个格子都会有两种采法,每种采法都会搜索海面一遍,复杂度可以上升到O(n3)。

自然就想到了,能不能在知道某一油面连通块的基础上,得到能采取多少次呢?实际上是可以的,考虑每一次采取的两格油块,他们的坐标和的奇偶性是不一样的,因此一个连通块能采取多少次取决于奇数和坐标与偶数和坐标的最小值。这样只需要遍历油面一遍即可。

代码

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

int T, N;
char grid[55][55];
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, 1, -1};
int ans, num0, num1;

void dfs(int x, int y) {
if (x < 0 || x >= N || y < 0 || y >= N || grid[x][y] == '.') return;
if ((x+y) & 1) num0 ++;
else num1 ++;
grid[x][y] = '.';

for (int i = 0; i < 4; i++) {

dfs(x+dx[i], y+dy[i]);
}
}
int main() {
//freopen("/Users/leey/Documents/cppProjects/input.in", "r", stdin);
scanf("%d", &T);
int cases = 0;
while (T--) {
cases++;
ans  = num0 = num1 = 0;

scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%s", grid[i]);
}

for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] == '.') continue;
num0 = num1 = 0;
dfs(i, j);
ans += min(num0, num1);
}
}
printf("Case %d: %d\n", cases, ans);
}
return 0;
}


B 道路建设

题目描述

最小生成树的基本题目。

思路

贴一个Blog:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html

学习Prim算法与Kruscal算法。两者都是贪心思想的产物,Prim是从点出发,定义一个点集合A初始为空,随意加入一个顶点作为起始顶点,然后选择不在A中的点和点A中的点距离最近的那个点,将其加入集合A中,然后更新最短距离;直到所有的点都加入集合中,时间复杂度为O(ElogV)。Kruscal算法是从边出发,首先将边按照权值顺序从小到大排序,然后依此加入集合中,如果加入的一条边和集中中已经存在的边形成了环路,就舍弃这条边。而判断是否形成环路可以用并查集来实现,时间复杂度为O(ElogE)。

代码

本题测试数据没有不存在最小生成树的情况,而且本题的测试数据中,两个点之间的距离可能会输入多次,应该取输入的最小值。

// Prim
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;

int c, n, m;
int w[105][105];
int vis[105], lowcost[105];

void prim(int start) {
bool tag = true;
for (int i = 1; i <= m; i++) {
vis[i] = 0;
lowcost[i] = w[start][i];
}
vis[start] = 1;
lowcost[start] = 0;
int k = 1, sum = 0;
while (k < m) {
int minw = inf, v = -1;
for (int i = 1; i <= m; i++) {
if (vis[i] == 0 && lowcost[i] < minw) {
minw = lowcost[i];
v = i;
}
}
if (v != -1) {
vis[v] = 1;
sum += lowcost[v];
k++;
for (int i = 1; i <= m; i++) {
if (vis[i] == 0){
lowcost[i] = min(lowcost[i], w[v][i]);
}
}
} else {
tag = false;
break;
}
}
puts(tag && sum <= c ? "Yes" : "No");
}
int main() {
while (scanf("%d %d %d", &c, &n, &m)  != EOF) {
memset(w, 0x3f3f3f3f, sizeof w);
int v1, v2, h;
for (int i = 0; i < n; i++) {
scanf("%d %d %d", &v1, &v2, &h);
if (w[v1][v2] > h) w[v1][v2] = w[v2][v1] = h;
}
prim(1);

}
return 0;
}

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

struct Edge {
int u, v;
int weight;

Edge(int _u, int _v, int _w) {
u = _u;
v = _v;
weight = _w;
}
bool operator < (const Edge &other) const {
return weight < other.weight;
}
};
vector<Edge> edges;
int c, n, m;
int root[105];
int Find(int x) {
if (root[x] == x) return x;
return root[x] = Find(root[x]);
}
void Union(int x, int y) {
int u = Find(x);
int v = Find(y);
if (u != v) {
root[v] = u;
}
}
int main() {
while (scanf("%d %d %d", &c, &n, &m) != EOF) {
for (int i = 1; i <= m; i++) {
root[i] = i;
}
edges.clear();

for (int i = 0; i < n; i++) {
int v1, v2, h;
scanf("%d %d %d", &v1, &v2, &h);
Edge tmp(v1, v2, h);
edges.push_back(tmp);
}
sort(edges.begin(), edges.end());
int res = 0;
for (int i = 0; i < edges.size(); i++) {
auto e = edges[i];
if (Find(e.u) != Find(e.v)) {
Union(e.u, e.v);
res += e.weight;
}
}
int t = 0;
for (int i = 1; i <= m; i++) {
if (root[i] == i) t++;
}
if (t > 1)
printf("No\n");
else
puts(res <= c ? "Yes" : "No");
}
return 0;
}


C 求交集

题目描述

求两个生序序列的交集。

思路

类似于合并排序的思想,时间复杂度为O(两个序列的长度和)。

D 小明的挖矿之旅

题目描述

给定一nxm网格,网格里由.或#组成,小明每次可以向右或向下移动一格且不能移动到#上,现在可以添加某一格子到任一格子到传送门,问你至少添加多少道传送门可以走完全部的.。

思路

抽象成数学描述:有向图加多少边让其强连通。注意到由于问题的特殊性,所以本题每一个格子就是一个连通块,即原图也就是其缩点后的图,从而省略了tarJan算法求强连通分量的这一步。接下来,添加的有向边数就是入度为0的点与出度为0的点的个数的最大值。遍历所有格子一遍即可。

注意特殊情况,当只有一个.时,不需要添加传送门,此时答案为0。

代码

#include <bits/stdc++.h>
using namespace std;
int n, m;
char s[1005][1005];
int in, out, single;

void judge(int x, int y) {
if ((x-1 < 0 || s[x-1][y] == '#') && (y-1<0 || s[x][y-1] == '#')) in++;
if ((x+1>=n || s[x+1][y] == '#') && (y+1>=m || s[x][y+1] == '#')) out++;
}
int main() {
while (~scanf("%d %d", &n, &m)) {
for (int i = 0; i < n; i++)
scanf("%s", s[i]);

in = out = single = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (s[i][j] == '#') continue;
single++;
judge(i, j);
}
}
printf("%d\n", single == 1 ? 0 : max(in, out));

}
return 0;
}


E 通知小弟

题目描述

一共有n个员工,BOSS可以联系到底下的m个员工,第i员工也能联系到ai个人,现在问你BOSS至少需要联系到多少员工可以将一条命令告诉全部员工。

思路

实际上是一个有向图,求取该有向图缩点图后的最小点基。对于Boss可以联系到的m个员工来说,如果第i个员工所在的强连通分量的入度为0(最高强连通分量,答案就加一,并将该强连通分量入度也加1,最后如果仍然存在入度为0的强连通分量,则Boss无法联系到所有员工。

代码

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

int n, m;
int tag[505], dfn[505], low[505];
stack<int> s;
int componentMap[505], in[505], vis[505];
int componentNum;
int t;
vector<int> adj[505];

void dfs(int u) {
dfn[u] = low[u] = t++;
vis[u] = 1;
s.push(u);
for (auto v : adj[u]) {
if (vis[v] == 0) {
dfs(v);
low[u] = min(low[u], low[v]);
}
if (vis[v] == 1) {
low[u] = min(low[u], low[v]);
}

}
if (dfn[u] == low[u]) {
componentNum++;
while (s.top() != u) {
int curr = s.top();
s.pop();
componentMap[curr] = componentNum;
vis[curr] = 2;
}
componentMap[s.top()] = componentNum;
vis[s.top()] = 2;
s.pop();
}
}
int main() {
//freopen("/Users/leey/Documents/cppProjects/input.in", "r", stdin);
while (~scanf("%d %d", &n, &m)) {
memset(vis, 0, sizeof vis);
memset(tag, 0, sizeof tag);
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof low);
memset(in, 0, sizeof in);
while (!s.empty()) s.pop();
for (int i = 1; i <= n; i++)
adj[i].clear();
componentNum = 0;
t = 0;

int id = 0;
for (int i = 0; i < m; i++) {
scanf("%d", &id);
tag[id] = 1;
}
for (int i = 1; i <= n; i++) {
int num;
scanf("%d", &num);
for (int j = 0; j < num; j++) {
scanf("%d", &id);
adj[i].push_back(id);
}
}

for (int i = 1; i <= n; i++) {
if (vis[i] == 0) dfs(i);
}

for (int u = 1; u <= n; u++) {
for (auto v : adj[u]) {
if (componentMap[u] != componentMap[v]) {
in[componentMap[v]]++;
}
}
}

int cnt = 0;
bool flag = true;
for (int i = 1; i <= n; i++) {
if (tag[i] && in[componentMap[i]] == 0) {
in[componentMap[i]]++;
cnt++;
}
}
//printf("componentNum = %d\n", componentNum);
for (int  i = 1; i <= componentNum; i++) {
if (in[i] == 0) {
flag = false;
break;
}
}

printf("%d\n", flag ? cnt : -1);
}
return 0;
}


奇奇怪怪

比赛中我是拿并查集AC的,因为我想的是虽然是有向图,但是一个集合中一定有一个代表,可以从代表联系到集合的所有人。就用这个代表来代表这个集合,同时维护集合的大小,最后判断集合的个数与集合的总大小来得到答案。(????

我还发现了另一种做法,首先在接收数据的时候更新in数组(代表第i个点的入度,然后若Boss的某个联系人入度为0,则Boss一定要联系他,同时将这个人所能联系到的所有人标志位置1;然后如果Boss的联系人中还有没有被联系的,则也需要联系;最后判断联系总人数是否等于n来得到是否可以联系到所有员
cedd
工。

贴两种做法的代码:

https://www.nowcoder.com/acm/contest/view-submission?submissionId=21559044

https://www.nowcoder.com/acm/contest/view-submission?submissionId=21530592

F Call To Your Teacher

题目描述

给你一有向图和两个点u和v,问你u是否可达v。

思路

dfs即可。

G 老子的意大利炮呢

题目描述

nxm地图,由路和障碍组成,给定起始点、终点以及3个中间点坐标,问你从起始点经过3个中间点并到达终点的最小时间。

其余条件:

1.3个点没有全部到达时,可以无视障碍物移动。

2.到达第3个中间点坐标后,只能走路。

思路

核心:最短路。考虑到第2个条件,考虑3个中间点到终点只能走路的最短路径。其次,只有6中顺序情况选择,前面2个中间点的距离就等于坐标差之和,因为他可以无视障碍物移动,维护全局时间最小值即可。

代码

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

const int inf = 0x3f3f3f3f;
int n, m;
char land[105][105];
int vis[105][105];
int sx, sy, ex, ey;
int t1, t2, t3;
int x[4], y[4], t[4];
int reachDis[4];

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

int order[][3] = {{1,2,3}, {1,3,2},{2,1,3}, {2,3,1}, {3,1,2}, {3,2,1}};
bool Go(int x, int y, int &dis) {
bool found = false;
queue<pair<int, int>> q;
q.push({x, y});
dis = 0;
int cnt = 1;
while (!q.empty()) {
if (cnt == 0) {
cnt = q.size();
dis++;
}
auto curr = q.front();
q.pop();
cnt--;
vis[curr.first][curr.second] = 1;
if (curr.first == ex && curr.second == ey) {
found = true;
break;
}
for (int i = 0; i < 4; i++) {
int nx = curr.first + dx[i];
int ny = curr.second + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
if (land[nx][ny] == '#' || vis[nx][ny]) continue;
q.push({nx, ny});
}

}
return found;
}
int main() {
while (~scanf("%d %d", &n, &m)) {
for (int i = 1; i <= n; i++)
scanf("%s", land[i]+1);
scanf("%d %d %d %d %d %d", &sx, &sy, &x[1], &y[1], &x[2], &y[2]);
scanf("%d %d %d %d", &x[3], &y[3], &ex, &ey);
scanf("%d %d %d", &t[1], &t[2], &t[3]);
for (int i = 1; i <= 3; i++) {
int dis = 0;
if (Go(x[i], y[i], dis)) {
//printf("%d can reach the end\n", i);
reachDis[i] = dis;
} else {
reachDis[i] = inf;
}
}
int ans = inf;
for (int i = 0; i < 6; i++) {
int a = order[i][0], b = order[i][1], c = order[i][2];
if (reachDis[c] == inf) continue;
///printf("%d can reach the end!\n", c);

int w = 1;
int tmp = (abs(sx-x[a])+abs(sy-y[a]))*w;
w += t[a];
tmp += (abs(x[a]-x)+abs(y[a]-y[b]))*w;
w += t[b];
tmp += (abs(x[b]-x[c])+abs(y[b]-y[c]))*w;
w += t[c];
tmp += reachDis[c]*w;
ans = min(ans, tmp);
}
printf("%d\n", ans);

//
}
return 0;
}


[b]小疑问


bfs的时候,比如树的层次遍历,怎么样知道自己遍历到第几层了呢?我记得在哪里看过,可是我想不起来了,我自己写的方法觉得奇奇怪怪得。

H 全排列

题目描述

按顺序输出1-8度全排列

思路

dfs+回溯。注意输出格式即可。

可以去LeetCode、LintCode上找找Permutation相关的题目,再看我做的代码我都想不起来自己怎么做的了。。。

总结

祝大家新年快乐吧!新的一年,希望踏踏实实提高算法能力,夯实基础。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: