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;
}

 

F - Call to your teacher

从$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;
}

 

posted @ 2018-02-11 17:22  Fighting_Heart  阅读(556)  评论(0编辑  收藏  举报