Atcoder Grand Contest 033 简要题解

A - Darker and Darker

多源最短路。

#include <bits/stdc++.h>

using namespace std;

const vector<int> dx = {1, 0, -1, 0};
const vector<int> dy = {0, 1, 0, -1};

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  vector<vector<int>> dist(n, vector<int>(m, -1));
  vector<string> board(n);
  queue<pair<int, int>> q;
  for (int i = 0; i < n; ++i) {
    cin >> board[i];
    for (int j = 0; j < m; ++j) {
      if (board[i][j] == '#') {
        dist[i][j] = 0;
        q.emplace(i, j);
      }
    }
  }
  int ans = 0;
  while (!q.empty()) {
    int x = q.front().first, y = q.front().second;
    ans = max(ans, dist[x][y]);
    q.pop();
    for (int d = 0; d < 4; ++d) {
      int xx = x + dx[d], yy = y + dy[d];
      if (xx >= 0 && xx < n && yy >= 0 && yy < m && dist[xx][yy] == -1) {
        dist[xx][yy] = dist[x][y] + 1;
        q.emplace(xx, yy);
      }
    }
  }
  cout << ans << "\n";
  return 0;
}

LRUD Game

注意到两维独立,然后倒着维护起始位置的合法区间即可。

#include <bits/stdc++.h>

using namespace std;

bool solve(int n, int s, int len, string foo, string bar, char add, char sub) {
  int l = 1, r = n;
  for (int i = len - 1; ~i; --i) {
    if (bar[i] == add) {
      --l;
    } else if (bar[i] == sub) {
      ++r;
    }
    l = max(l, 1);
    r = min(r, n);
    if (foo[i] == add) {
      --r;
    } else if (foo[i] == sub) {
      ++l;
    }
    if (l > r) {
      return false;
    }
  }
  return l <= s && s <= r;
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int r, c, n, x, y;
  string foo, bar;
  cin >> r >> c >> n >> x >> y >> foo >> bar;
  cout << (solve(r, x, n, foo, bar, 'D', 'U') && solve(c, y, n, foo, bar, 'R', 'L') ? "YES" : "NO") << "\n";
  return 0;
}

Removing Coins

注意到每个人可以把直径减少 1 1 1 或者 2 2 2 ,然后判一下直径模 3 3 3 就行了。

#include <bits/stdc++.h>

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<vector<int>> adj(n);
  for (int i = 0; i < n - 1; ++i) {
    int from, to;
    cin >> from >> to;
    --from;
    --to;
    adj[from].push_back(to);
    adj[to].push_back(from);
  }
  vector<int> dist(n, -1);
  function<void(int)> dfs = [&](int x) {
    for (auto y : adj[x]) {
      if (dist[y] == -1) {
        dist[y] = dist[x] + 1;
        dfs(y);
      }
    }
  };
  dist[0] = 0;
  dfs(0);
  int root = max_element(dist.begin(), dist.end()) - dist.begin();
  dist.assign(n, -1);
  dist[root] = 0;
  dfs(root);
  int diameter = *max_element(dist.begin(), dist.end());
  cout << (diameter % 3 == 1 ? "Second" : "First") << "\n";
  return 0;
}

Complexity

注意到答案不大,记 f ( k , l , r , h ) f(k, l,r,h) f(k,l,r,h) 表示考虑上下边界为 l , r l,r l,r ,左边界为 h h h ,答案为 k k k 时最大可能的右端点。转移可以二分,也可以用单调性。

#include <bits/stdc++.h>

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  vector<string> board(n);
  for (int i = 0; i < n; ++i) {
    cin >> board[i];
  }
  vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>(m)));
  for (int l = n - 1; ~l; --l) {
    for (int r = l; r < n; ++r) {
      for (int h = m - 1; ~h; --h) {
        if (l == r || (board[l][h] == board[l + 1][h] && dp[l + 1][r][h] > h)) {
          if (h + 1 < m && board[l][h] == board[l][h + 1]) {
            dp[l][r][h] = dp[l][r][h + 1];
          } else {
            dp[l][r][h] = h + 1;
          }
        } else {
          dp[l][r][h] = h;
        }
      }
    }
  }
  int ans = 0;
  while (dp[0][n - 1][0] < m) {
    vector<vector<vector<int>>> new_dp(n, vector<vector<int>>(n, vector<int>(m)));
    for (int l = n - 1; ~l; --l) {
      for (int r = l; r < n; ++r) {
        for (int h = m - 1; ~h; --h) {
          new_dp[l][r][h] = dp[l][r][h] < m ? dp[l][r][dp[l][r][h]] : m;
          if (l < r) {
            int low = l, high = r - 1;
            while (low <= high) {
              int mid = (low + high) >> 1;
              new_dp[l][r][h] = max(new_dp[l][r][h], min(dp[l][mid][h], dp[mid + 1][r][h]));
              if (dp[l][mid][h] > dp[mid + 1][r][h]) {
                low = mid + 1;
              } else {
                high = mid - 1;
              }
            }
          }
        }
      }
    }
    swap(dp, new_dp);
    ans++;
  }
  cout << ans << "\n";
  return 0;
}

Go around a Circle

特判全是一个字母的情况,假设这个串以 R R R 开头,不难发现:

  • 环一定由若干段组成,每段是奇数个 R R R 后面接一个 B B B
  • 连续的 R R R 长度有限制。

然后就可以对着这个做DP了。

#include <bits/stdc++.h>

using namespace std;

const int md = (int) 1e9 + 7;

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

inline void sub(int &x, int y) {
  x -= y;
  if (x < 0) {
    x += md;
  }
}

inline int mul(int x, int y) {
  return (int) ((long long) x * y % md);
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  string s;
  cin >> n >> m >> s;
  if (s[0] == 'B') {
    for (auto &c : s) {
      c ^= 'R' ^ 'B';
    }
  }
  int first = m;
  for (int i = 0; i < m; ++i) {
    if (s[i] == 'B') {
      first = i;
      break;
    }
  }
  if (first == m) {
    vector<int> dp(4);
    dp[0] = dp[3] = 1;
    for (int i = 1; i < n; ++i) {
      vector<int> new_dp(4);
      for (int a = 0; a < 2; ++a) {
        for (int b = 0; b < 2; ++b) {
          for (int c = 0; c < 2; ++c) {
            if (!b || !c) {
              add(new_dp[a * 2 + b], dp[a * 2 + c]);
            }
          }
        }
      }
      swap(dp, new_dp);
    }
    int ans = 0;
    for (int i = 0; i < 3; ++i) {
      add(ans, dp[i]);
    }
    cout << ans << "\n";
    return 0;
  }
  int last = m - 1;
  while (s[last] == 'R') {
    --last;
  }
  int limit = n, cur = 0;
  for (int i = last; i >= -1; --i) {
    if (i == -1 || s[i] == 'B') {
      if (cur) {
        if (cur & 1) {
          limit = min(limit, cur + 1);
        } else if (i == -1) {
          limit = min(limit, cur + 2);
        }
        cur = 0;
      }
    } else {
      ++cur;
    }
  }
  vector<int> dp(n + 1);
  dp[0] = dp[2] = 1;
  for (int i = 4; i <= n; i += 2) {
    dp[i] = mul(dp[i - 2], 2);
    if (i >= limit + 2) {
      sub(dp[i], dp[i - (limit + 2)]);
    }
  }
  int ans = 0;
  for (int i = 2; i <= limit; i += 2) {
    add(ans, mul(dp[n - i], i));
  }
  cout << ans << "\n";
  return 0;
}

Adding Edges

主要思路是,考虑压缩 G G G ,如果存在边 ( a , b ) , ( a , c ) (a,b), (a,c) (a,b),(a,c) 且在树上 a , b , c a,b,c a,b,c 按照这样的顺序排成了一条路径,那么删去 G G G 中的 ( a , c ) (a,c) (a,c) ,加上 ( b , c ) (b,c) (b,c) ,直到不能操作为止,那么我们有一个结论:

  • 在最终的图中, ( x , y ) (x,y) (x,y) 有边当且仅当存在一个序列 x , a 1 , a 2 , ⋯ &ThinSpace; , a k , y x,a_1, a_2, \cdots, a_k, y x,a1,a2,,ak,y 满足这个序列相邻两个在压缩后的 G G G 中有边;且这个序列在树上对应一条路径,顺序正好是路径上这些点出现的顺序。

如果将图压缩了,答案就很好统计了。考虑压缩这个图,记 t o p ( a , b ) top(a,b) top(a,b) 表示以 a a a 为根,离 b b b 最近的与 a a a 直接相连的祖先,那么加一条边 ( a , b ) (a,b) (a,b) 时,如果 t o p ( a , b ) top(a,b) top(a,b) 存在 ,这条边就不用加,只需要加 ( b , t o p ( a , b ) ) (b,top(a,b)) (b,top(a,b)) ,否则将 b b b 子树打上标记,碰到边时压缩即可。具体细节可以参考代码。

#include <bits/stdc++.h>

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  vector<vector<int>> adj(n);
  for (int i = 0; i < n - 1; ++i) {
    int from, to;
    cin >> from >> to;
    --from;
    --to;
    adj[from].push_back(to);
    adj[to].push_back(from);
  }
  vector<vector<int>> parent(n, vector<int>(n, -1));
  for (int i = 0; i < n; ++i) {
    function<void(int)> dfs = [&](int x) {
      for (auto y : adj[x]) {
        if (y != parent[i][x]) {
          parent[i][y] = x;
          dfs(y);
        }
      }
    };
    dfs(i);
  }
  vector<vector<bool>> graph(n, vector<bool>(n));
  vector<vector<int>> top(n, vector<int>(n, -1));
  function<void(int, int)> add = [&](int from, int to) {
    if (top[from][to] == to || top[to][from] == from) {
      return;
    } else if (top[from][to] != -1) {
      add(top[from][to], to);
    } else if (top[to][from] != -1) {
      add(top[to][from], from);
    } else {
      vector<pair<int, int>> edges;
      for (int rep = 0; rep < 2; ++rep) {
        graph[from][to] = true;
        top[from][to] = to;
        queue<int> q;
        q.push(to);
        while (!q.empty()) {
          int x = q.front();
          q.pop();
          for (auto y : adj[x]) {
            if (y != parent[from][x]) {
              if (top[from][y] == -1) {
                top[from][y] = to;
                q.push(y);
              } else if (graph[from][y]) {
                graph[from][y] = graph[y][from] = false;
                edges.emplace_back(to, y);
              }
            }
          }
        }
        swap(from, to);
      }
      for (auto p : edges) {
        add(p.first, p.second);
      }
    }
  };
  for (int i = 0; i < m; ++i) {
    int from, to;
    cin >> from >> to;
    --from;
    --to;
    add(from, to);
  }
  int ans = 0;
  for (int i = 0; i < n; ++i) {
    function<void(int, int)> dfs = [&](int x, int t) {
      if (top[t][x] == x) {
        ++ans;
        t = x;
      }
      for (auto y : adj[x]) {
        if (y != parent[i][x]) {
          dfs(y, t);
        }
      }
    };
    for (auto j : adj[i]) {
      dfs(j, i);
    }
  }
  cout << ans / 2 << "\n";
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值