2019 ICPC Asia Nanjing Regional

A B C D E F G H I J K
O Ø O x x O x x O Ø O
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • x 没有尝试

A. A Hard Problem

选择较大的一半当作答案即可$⌈ \frac{n}{2} ⌉$。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;
// mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count());

void solve() {
  int n;
  cin >> n;
  cout << (n - 1) / 2 + 2 << "\n";
}

int main(int argc, char *argv[]) {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int o_o;
  for (cin >> o_o; o_o; --o_o) solve();
  return 0;
}

B. Chessboard

首先,我们可以观察到如下几个不难证明的性质:

  1. 两个被染上颜色的格子之间的距离始终为:曼哈顿距离。
  2. 一个染色方案是合法的,当且仅当:任何时刻,每行/列被染色的格子要么不存在,要么是连续的一段。
  3. 如果当前被染色的区域是个矩形,那么最后一个被染色的点,一定在角上。
  4. 如果当前被染色的区域是个$r(r ≥ 2)$行$c(c ≥ 2)$列的矩形,

那么接下来要么扩充成$r + 1$行$c$列的矩阵(扩充行),要么扩充成$r$行$c+1$列的矩形(扩充列)。并且根据当前状态中最后一个被染色的格子在哪个角上,扩充行/列的方案都是唯一的。

整个过程一共要扩充$n + m - 2$次,其中$n - 1$次是行,每次有四个角落,所以答案是$4 × {n + m - 2 \choose n - 1}$。注意特判 1。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>
using namespace std;

const int MOD = 1e9 + 7;
const int MAXN = 2e6 + 3;

int fac[MAXN], inv[MAXN], fac_inv[MAXN];

int main(int argc, char *argv[]) {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  fac[0] = fac[1] = 1;
  inv[1] = fac_inv[0] = fac_inv[1] = 1;
  for (int i = 2; i < MAXN; ++i) {
    fac[i] = 1ll * fac[i - 1] * i % MOD;
    inv[i] = 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD;
    fac_inv[i] = 1ll * fac_inv[i - 1] * inv[i] % MOD;
  }

  int o_o, n, m;
  for (cin >> o_o; o_o; --o_o) {
    cin >> n >> m;
    if (n == 1 && m == 1) {
      cout << "1\n";
    } else if (n == 1 || m == 1) {
      cout << "2\n";
    } else {
      cout << 4ll * fac[n + m - 2] * fac_inv[n - 1] % MOD * fac_inv[m - 1] % MOD
           << "\n";
    }
  }

  return 0;
}

C. Digital Path

按照值排序后 DP 即可,头四个距离要特殊处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e3 + 3;
const int MOD = 1e9 + 7;

int f[4][MAXN][MAXN], a[MAXN][MAXN];
pair<int, int> p[MAXN * MAXN];

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int n, m, cnt = 0;
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      cin >> a[i][j];
      p[cnt++] = make_pair(a[i][j], i << 10 | j);
    }
  }
  sort(p, p + cnt);

  int ans = 0;
  for (int i = 0; i < cnt; ++i) {
    int v = p[i].first;
    int x = p[i].second >> 10, y = p[i].second & 1023;

    static int dx[] = {0, 1, 0, -1};
    static int dy[] = {1, 0, -1, 0};
    static auto valid = [&](const int &x, const int &y) {
      return 1 <= x && x <= n && 1 <= y && y <= m;
    };
    static auto add = [&](int &a, int b) {
      a += b;
      if (a >= MOD) a -= MOD;
    };
    bool fg1 = true, fg2 = true;
    for (int i = 0; i < 4; ++i) {
      int x_ = x + dx[i];
      int y_ = y + dy[i];
      if (!valid(x_, y_)) continue;
      if (a[x_][y_] != v - 1) {
        fg1 &= a[x_][y_] != v + 1;
        continue;
      }
      fg2 = false;
      for (int j = 0; j < 3; ++j) add(f[j + 1][x][y], f[j][x_][y_]);
      add(f[3][x][y], f[3][x_][y_]);
    }
    if (fg1) {
      // cerr << "# " << x << " " << y << " " << f[3][x][y] << endl;
      add(ans, f[3][x][y]);
    }
    if (fg2) { f[0][x][y] = 1; }
  }

  cout << ans << "\n";

  return 0;
}

F. Paper Grading

本质上是询问一个 Trie 树的子树中区间内的点值有多少个,为了支持修改,使用树状数组套线段树即可。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 3;

struct Node {
  int sum;
  Node *left, *right;
} pool[MAXN * 100], *pool_ptr, *nil, *root[MAXN];

Node *newNode() {
  *pool_ptr = {0, nil, nil};
  return pool_ptr++;
}

void update(Node *(&u), int l, int r, int pos, int delta) {
  if (u == nil) u = newNode();
  u->sum += delta;
  if (l == r) return;
  int mid = (l + r) / 2;
  if (pos <= mid)
    update(u->left, l, mid, pos, delta);
  else
    update(u->right, mid + 1, r, pos, delta);
  // cerr << l << " " << r << " " << pos << " " << delta << " " << u->sum <<
  // "\n";
}
int query(Node *u, int l, int r, int x, int y) {
  if (u == nil) return 0;
  if (x <= l && r <= y) return u->sum;
  int mid = (l + r) / 2, res = 0;
  if (x <= mid) res = query(u->left, l, mid, x, y);
  if (y > mid) res += query(u->right, mid + 1, r, x, y);
  return res;
}

int n, ch[MAXN][26], pos[MAXN], in[MAXN], out[MAXN], tot, dfn;

void update(int x, int pos, int delta) {
  for (; x <= n; x += x & -x) update(root[x], 1, tot, pos, delta);
}
int query(int x, int l, int r) {
  int res = 0;
  for (; x > 0; x &= x - 1) res += query(root[x], 1, tot, l, r);
  return res;
}

void init() {
  tot = 1;
  memset(ch, -1, sizeof(ch));
  pool_ptr = pool;
  nil = newNode();
  nil->left = nil->right = nil;
  fill(root + 1, root + n + 1, nil);
}

void dfs(int u) {
  in[u] = ++dfn;
  for (int i = 0; i < 26; ++i) {
    int v = ch[u][i];
    if (v < 0) continue;
    dfs(v);
  }
  out[u] = dfn;
}

int main() {
  // freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int m;
  cin >> n >> m;
  init();

  static char buf[MAXN];
  for (int i = 1; i <= n; ++i) {
    cin >> buf;
    int u = 0;
    for (char *it = buf; *it; ++it) {
      int chr = *it - 'a';
      if (ch[u][chr] < 0) ch[u][chr] = tot++;
      u = ch[u][chr];
    }
    pos[i] = u;
  }

  dfs(0);
  for (int i = 1; i <= n; ++i) update(i, in[pos[i]], 1);
  // cerr << "########" << endl;
  for (int opt, i, j, k; m; --m) {
    cin >> opt;
    if (opt == 1) {
      cin >> i >> j;
      if (i == j) continue;
      update(i, in[pos[i]], -1);
      update(i, in[pos[j]], 1);
      update(j, in[pos[j]], -1);
      update(j, in[pos[i]], 1);
      swap(pos[i], pos[j]);
    } else {
      cin >> buf >> k >> i >> j;
      int u = 0;
      for (int step = 0; (~u) && step < k; ++step) {
        int chr = buf[step] - 'a';
        u = ch[u][chr];
      }
      if (u < 0) {
        cout << "0\n";
      } else {
        // cerr << in[u] << " " << out[u] << endl;
        int l = in[u] ? in[u] : 1, r = out[u];
        if (l > r) {
          cout << "0\n";
        } else {
          cout << query(j, in[u], out[u]) - query(i - 1, in[u], out[u]) << "\n";
        }
      }
    }
  }

  return 0;
}

H. Prince and Princess

智力题。 这篇文章讲的比我不知道高到哪里去了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <bits/stdc++.h>
using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int a, b, c;
  cin >> a >> b >> c;
  if (a + b + c == 1) {
    cout << (a ? "YES\n0\n" : "NO\n");
  } else if (b + c >= a) {
    cout << "NO\n";
  } else {
    cout << "YES\n";
    cout << (b + c) * 2 + 1 << "\n";
  }

  return 0;
}

J. Spy

最大权匹配的模板题,记得找一个好一点的板子。 网上好多板子是假的。。。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 400 + 3;
const int ALPHA = 1e9 + 10;
const ll NOT = 0;
const ll INF = 3ll * MAXN * ALPHA;

int n, nl, nr, lk[MAXN], pre[MAXN];
ll lx[MAXN], ly[MAXN], w[MAXN][MAXN], slack[MAXN];
bitset<MAXN> vy;

void init(int n) {
  ::n = n;
  memset(lk, 0, sizeof(int) * (n + 1));
  memset(pre, 0, sizeof(int) * (n + 1));
  memset(lx, 0, sizeof(ll) * (n + 1));
  memset(ly, 0, sizeof(ll) * (n + 1));
  memset(slack, 0, sizeof(ll) * (n + 1));
  for (int i = 0; i <= n; ++i) fill(w[i], w[i] + n + 1, NOT);
}

void add_edge(int x, int y, ll z) {
  if (w[y][x] < z) w[y][x] = z;
}

ll KM() {
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j) lx[i] = max(lx[i], w[i][j]);
  for (int i = 1, py, p, x; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) slack[j] = INF, vy[j] = 0;
    for (lk[py = 0] = i; lk[py]; py = p) {
      ll delta = INF;
      vy[py] = 1, x = lk[py];
      for (int y = 1; y <= n; ++y) {
        if (vy[y]) continue;
        if (lx[x] + ly[y] - w[x][y] < slack[y])
          slack[y] = lx[x] + ly[y] - w[x][y], pre[y] = py;
        if (slack[y] < delta) delta = slack[y], p = y;
      }
      for (int y = 0; y <= n; ++y)
        if (vy[y]) {
          lx[lk[y]] -= delta, ly[y] += delta;
        } else {
          slack[y] -= delta;
        }
    }
    for (; py; py = pre[py]) lk[py] = lk[pre[py]];
  }

  ll ans = 0;
  for (int i = 1; i <= n; ++i) {
    ans += lx[i] + ly[i];
    if (w[lk[i]][i] == NOT) ans -= NOT;
  }
  return ans;
}

int sum[MAXN];
ll b[MAXN], c[MAXN];
pair<ll, int> ap[MAXN];

int main(int argc, char *argv[]) {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int n;
  cin >> n;
  for (int i = 1; i <= n; ++i) cin >> ap[i].first;
  for (int i = 1; i <= n; ++i) cin >> ap[i].second;

  sum[0] = 0;
  sort(ap + 1, ap + n + 1);
  for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + ap[i].second;

  for (int i = 1; i <= n; ++i) cin >> b[i];
  for (int i = 1; i <= n; ++i) cin >> c[i];

  init(n);
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j) {
      ll bc = b[i] + c[j];
      int idx = lower_bound(ap + 1, ap + n + 1, make_pair(bc, INT_MIN)) - ap;
      add_edge(i, j, sum[idx - 1]);
    }

  cout << KM() << "\n";

  return 0;
}

K. Triangle

很容易猜到一个性质:平分线必过重心。可惜是假的(正三角形就可以搞出反例),要使用二分搞(为什么每次板子都会缺要看的那一页)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <bits/stdc++.h>
using namespace std;

const double EPS = 1e-9;

int sign(double x) { return fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1); }

struct Vector {
  double x, y;

  Vector(double x = 0, double y = 0) : x(x), y(y) {}
  Vector operator-(const Vector &other) const {
    return Vector(x - other.x, y - other.y);
  }
  Vector operator+(const Vector &other) const {
    return Vector(x + other.x, y + other.y);
  }
  Vector operator*(const double &rate) const {
    return Vector(x * rate, y * rate);
  }
  double dot(const Vector &other) const { return x * other.x + y * other.y; }
  double det(const Vector &other) const { return x * other.y - y * other.x; }
  double len() const { return hypot(x, y); }
};

struct Segment {
  Vector s, t;

  Segment() {}
  Segment(Vector s, Vector t) : s(s), t(t) {}
  bool onSegment(const Vector &p) const {
    Vector ps = s - p, pt = t - p;
    return sign(ps.det(pt)) == 0 && sign(ps.dot(pt)) <= 0;
  }

  Vector inter(const Segment &other) const {
    Vector p1 = t - s, p2 = other.t - s;
    if (sign(p1.det(p2)) == 0) return Vector(-1e9, 0);
    double area1 = fabs(p1.det(other.s - s));
    double area2 = fabs(p1.det(other.t - s));
    double x = (area1 * other.t.x + area2 * other.s.x) / (area1 + area2);
    double y = (area1 * other.t.y + area2 * other.s.y) / (area1 + area2);
    return Vector(x, y);
  }
};

double area(Vector a, Vector b, Vector c) { return fabs((a - c).det(a - b)); }

void gao(Vector a, Vector b, Vector c, Vector p) {
  if (sign((p - a).len() - (p - b).len()) > 0) swap(a, b);
  double l = 0, r = 1;
  Vector cb = c - b, q;
  for (int step = 0; step < 200 && sign(r - l); ++step) {
    double mid = (l + r) / 2;
    q = b + cb * mid;
    if (sign(area(a, p, q) + area(a, q, c) - area(p, q, b)) <= 0) {
      r = mid;
    } else {
      l = mid;
    }
  }
  cout << q.x << " " << q.y << "\n";
}

void solve() {
  Vector a, b, c, p;
  cin >> a.x >> a.y >> b.x >> b.y >> c.x >> c.y >> p.x >> p.y;
  Segment ab(a, b), ac(a, c), bc(b, c);
  if (ab.onSegment(p)) {
    gao(a, b, c, p);
  } else if (ac.onSegment(p)) {
    gao(a, c, b, p);
  } else if (bc.onSegment(p)) {
    gao(b, c, a, p);
  } else {
    cout << "-1\n";
  }
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  cout << fixed << setprecision(10);

  int o_o;
  for (cin >> o_o; o_o; --o_o) solve();

  return 0;
}
comments powered by Disqus