2018年全国多校算法寒假训练营练习比赛(第四场)题解
【题目链接】
A - 石油采集
题意:有一个$01$矩阵,每次可以拿走两个相邻的$1$,问最多能操作几次。
这题和HDU 1507一样。二维矩阵四连通图是一个二分图,题目的操作事实上就是求这个二分图的最大匹配。
B - 道路建设
最小生成树
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 10; int f[maxn]; struct Edge { int u, v, cost; }s[maxn]; bool cmp(Edge &a, Edge &b) { return a.cost < b.cost; } int Find(int x ){ if(x != f[x]) f[x] = Find(f[x]); return f[x]; } int main() { int c, n, m; while(~scanf("%d%d%d", &c, &n, &m)) { for(int i = 1; i <= m; i ++) { f[i] = i; } for(int i = 1; i <= n; i ++) { scanf("%d%d%d", &s[i].u, &s[i].v, &s[i].cost); } sort(s + 1, s+1+n, cmp); for(int i =1; i <= n; i ++) { int fu = Find(s[i].u); int fv = Find(s[i].v); if(fu == fv) continue; f[fu] = fv; c -= s[i].cost; if(c < 0) break; } if(c < 0) printf("No\n"); else printf("Yes\n"); } return 0; }
C - 求交集
类似于归并排序那样搞就可以了。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int a[maxn], b[maxn], ans[maxn]; int n, m, sz; int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 1; i <= n; i ++) scanf("%d", &a[i]); for(int i = 1; i <= m; i ++) scanf("%d", &b[i]); int p1 = 1, p2 = 1; sz = 0; while(p1 <= n && p2 <= m) { if(a[p1] == b[p2]) { ans[sz ++] = a[p1]; p1 ++; p2 ++; } else { if(a[p1] > b[p2]) p2 ++; else p1 ++; } } if(sz == 0) { printf("empty\n"); continue; } for(int i = 0; i < sz; i ++) { printf("%d", ans[i]); if(i < sz - 1) printf(" "); else printf("\n"); } } return 0; }
D - 小明的挖矿之旅
由于只能向右或者向下走,所以变成了一张有向无环图。我们只要比较出度为$0$的点的个数和入度为$0$的点的个数即可。
但要注意几种特殊情况:全是孤立点、没有点。
#include <bits/stdc++.h> using namespace std; const int INF = 0x7FFFFFFF; const int maxn = 1100; char s[maxn][maxn]; int in[maxn * maxn]; int ou[maxn * maxn]; int n, m; int out(int x, int y) { if(x < 0 || x >= n) return 1; if(y < 0 || y >= m) return 1; return 0; } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 0; i < n; i ++) { scanf("%s", s[i]); } memset(in, 0, sizeof in); memset(ou, 0, sizeof ou); for(int i = 0; i < n; i ++) { for(int j = 0; j < m; j ++) { if(s[i][j] == '#') continue; if(out(i, j + 1) == 0 && s[i][j + 1] != '#') { in[i * m + j + 1] ++; ou[i * m + j] ++; } if(out(i + 1, j) == 0 && s[i + 1][j] != '#') { in[(i + 1) * m + j] ++; ou[i * m + j] ++; } } } int sum1 = 0, sum2 = 0, sum3 = 0; for(int i = 0; i < n; i ++) { for(int j = 0; j < m; j ++) { if(s[i][j] == '#') continue; if(in[i * m + j] == 0) sum1 ++; if(ou[i * m + j] == 0) sum2 ++; sum3 ++; } } if(sum3 == 0) { printf("%d\n", 0); return 0; } if(sum1 == sum3 && sum2 == sum3) { printf("%d\n", sum1 - 1); } else printf("%d\n", max(sum1, sum2)); } return 0; }
E - 通知小弟
先对图进行强连通分量缩点,因为一个强连通分量内部一旦有一个人得到通知,所有人都可以得到通知。
缩点后形成了一张有向无环图,我们只要通知到新图中那些入度为$0$的点,所有的人都能得到通知。
对于无解的情况,我们只要检查HA能通知到的人是否cover了新图中所有入度为$0$的点。
#include <bits/stdc++.h> using namespace std; const int maxn = 550; int n, m; int a[maxn]; int ans[maxn]; int in[maxn]; #define MAXN 550 struct Node { int v; int next; } edge[500010]; int sz; int head[MAXN]; int dfn[MAXN]; int low[MAXN]; bool mark[MAXN]; //to judge in stack or not int id[MAXN]; //the id of scc int T; stack<int> sta; int scc; void tarjan(int v) { dfn[v] = low[v] = ++T; sta.push(v); mark[v] = true; int k; for (k = head[v]; k != -1; k = edge[k].next) { int u = edge[k].v; if (!dfn[u]) { tarjan(u); low[v] = min(low[v], low[u]); } else if (mark[u]) low[v] = min(low[v], dfn[u]); } if (low[v] == dfn[v]) { ++scc; int u; do { u = sta.top(); sta.pop(); mark[u] = false; id[u] = scc; } while (u != v); } return; } void solve(int n) { scc = 0; int i; for (i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i); memset(mark, false, sizeof (mark)); int k; for (i = 1; i <= n; ++i) for (k = head[i]; k != -1; k = edge[k].next) if (id[i] != id[edge[k].v]) mark[id[i]] = true; return ; for(int i = 1; i <= n; i ++) { printf("%d : %d\n", i, id[i]); } int p; int cnt(0); for (i = 1; i <= scc; ++i) { if (!mark[i]) { ++cnt; p = i; } } if (cnt > 1) printf("0\n"); else { cnt = 0; for (i = 1; i <= n; ++i) if (id[i] == p) ++cnt; printf("%d\n", cnt); } } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 1; i <= m; i ++) { scanf("%d", &a[i]); } for (int i = 1; i <= n; ++i) { head[i] = -1; dfn[i] = 0; mark[i] = false; } sz = 0; for (int i = 1; i <= n; ++i) { int num; scanf("%d", &num); while(num --) { int to; scanf("%d", &to); edge[sz].v = to; edge[sz].next = head[i]; head[i] = sz ++; } } solve(n); memset(in, 0, sizeof in); memset(ans, 0, sizeof ans); for(int i = 1; i <= n; i ++) { for(int j = head[i]; j != -1; j = edge[j].next) { if(id[i] != id[edge[j].v]) in[id[edge[j].v]] ++; } } int pp = 0; for(int i = 1; i <= scc; i ++) { if(in[i] == 0) pp ++; } for(int i = 1; i <= m; i ++) { if(in[id[a[i]]] == 0) { ans[id[a[i]]] = 1; } } int out = 0; for(int i = 1; i <= scc; i ++) { out += ans[i]; } if(out == pp) {} else out = -1; cout << out << endl; } return 0; }
从$1$号点开始$dfs$,如果$n$能被遍历到就是可以的。
#include <bits/stdc++.h> using namespace std; const int maxn = 500; int n, m; vector<int> g[maxn]; int f[maxn]; void dfs(int x) { f[x] = 1; for(int i = 0; i < g[x].size(); i ++) { if(f[g[x][i]]) continue; dfs(g[x][i]); } } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 1; i <= n; i ++) { g[i].clear(); f[i] = 0; } while(m --) { int x, y; scanf("%d%d", &x, &y); g[x].push_back(y); } dfs(1); if(f[n]) printf("Yes\n"); else printf("No\n"); } return 0; }
G - 老子的意大利炮呢
可以枚举三种配件按什么顺序获得,得到之后再走到终点即可。最后阶段可以bfs。
#include <bits/stdc++.h> using namespace std; const int INF = 0x7FFFFFFF; const int maxn = 110; char s[maxn][maxn]; int dis[maxn][maxn]; int ans; int n, m; int sx, sy; int x[5], y[5]; int ex, ey; int t[5]; int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1}, }; int out(int x, int y) { if(x < 0 || x >= n) return 1; if(y < 0 || y >= m) return 1; return 0; } void bfs() { queue<int> q; q.push(ex * m + ey); dis[ex][ey] = 0; while(!q.empty()) { int top = q.front(); q.pop(); int nowx = top / m; int nowy = top % m; for(int i = 0; i < 4; i ++) { int tx = nowx + dir[i][0]; int ty = nowy + dir[i][1]; if(out(tx, ty)) continue; if(s[tx][ty] == '#') continue; if(dis[nowx][nowy] + t[0] + t[1] + t[2] + 1 > dis[tx][ty]) continue; dis[tx][ty] = dis[nowx][nowy] + t[0] + t[1] + t[2] + 1; q.push(tx * m + ty); } } } int D(int x1, int y1, int x2, int y2) { return abs(x1 - x2) + abs(y1 - y2); } int main() { scanf("%d%d", &n, &m); for(int i = 0; i < n; i ++) { scanf("%s", s[i]); for(int j = 0; j < m; j ++) { dis[i][j] = INF; } } scanf("%d%d", &sx, &sy); sx --; sy --; for(int i = 0; i < 3; i ++) { scanf("%d%d", &x[i], &y[i]); x[i] --; y[i] --; } scanf("%d%d", &ex, &ey); ex --; ey --; for(int i = 0; i < 3; i ++) { scanf("%d", &t[i]); } bfs(); /* for(int i = 0; i< n; i ++) { for(int j = 0; j < m; j ++) { printf("%d ", dis[i][j]); } printf("\n"); } */ ans = INF; for(int i = 0; i < 3; i ++) { if(dis[x[i]][y[i]] == INF) continue; int tmp = 0; int p0, p1, p2; if(i == 0) { p0 = 0; p1 = 1; p2 = 2; } else if(i == 1) { p0 = 1; p1 = 0; p2 = 2; } else { p0 = 2; p1 = 1; p2 = 0; } tmp = min(D(sx, sy, x[p1], y[p1]) * 1 + D(x[p1], y[p1], x[p2], y[p2]) * (t[p1] + 1) + D(x[p2], y[p2], x[p0], y[p0]) * (t[p1] + t[p2] + 1), D(sx, sy, x[p2], y[p2]) * 1 + D(x[p1], y[p1], x[p2], y[p2]) * (t[p2] + 1) + D(x[p1], y[p1], x[p0], y[p0]) * (t[p1] + t[p2] + 1) ); tmp += dis[x[i]][y[i]]; ans = min(ans, tmp); } cout << ans << endl; return 0; }
H - 老子的全排列呢
可以手写$dfs$生成,也可以调用函数。
#include <bits/stdc++.h> using namespace std; int a[10]; int main() { for(int i = 1; i <= 8; i ++) { a[i] = i; } do { for(int i = 1; i <= 8; i ++) { cout << a[i]; if(i < 8) cout << " "; } cout << endl; } while(next_permutation(a + 1, a + 9)); return 0; }