Atcoder Grand Contest 028 简要题解

Two Abbreviations

答案要么是 − 1 -1 1 要么是 l c m ( n , m ) lcm(n, m) lcm(n,m)

#include <bits/stdc++.h>

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n, m;
  string s, t;
  cin >> n >> m >> s >> t;
  int gcd = __gcd(n, m);
  for (int i = 0, j = 0; i < n && j < m; i += n / gcd, j += m / gcd) {
    if (s[i] != t[j]) {
      cout << -1 << endl;
      return 0;
    }
  }
  cout << (long long)n * m / gcd << endl;
  return 0;
}

Removing Blocks

考虑 i i i 在删除 j ( i ≤ j ) j(i\le j) j(ij) 时产生贡献的概率,就是 j j j i , i + 1 , ⋯ j i, i+1, \cdots j i,i+1,j 中最早被删除的概率,即 1 j − i + 1 \frac{1}{j-i+1} ji+11 。当 i i i 移动的时候,系数改变是 O ( 1 ) O(1) O(1) 的。

#include <bits/stdc++.h>

using namespace std;

const int md = 1e9 + 7;

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

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

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

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  cin >> n;
  vector<int> a(n);
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
  }
  vector<int> inv(n + 1);
  inv[0] = inv[1] = 1;
  for (int i = 2; i <= n; ++i) {
    inv[i] = mul(md - md / i, inv[md % i]);
  }
  int coef = 0;
  for (int i = 1; i <= n; ++i) {
    coef = add(coef, inv[i]);
  }
  int answer = 0;
  for (int i = 0; i < n; ++i) {
    answer = add(answer, mul(coef, a[i]));
    if (i + 1 < n) {
      coef = add(coef, inv[i + 2]);
      coef = sub(coef, inv[n - i]);
    }
  }
  for (int i = 1; i <= n; ++i) {
    answer = mul(answer, i);
  }
  cout << answer << endl;
  return 0;
}

Min Cost Cycle

首先 min ⁡ ( a x , b y ) \min(a_x, b_y) min(ax,by) 可以认为是 a x + b y a_x + b_y ax+by 再减去任意一个。那么我们相当于要选出 n n n 个位置,在合法的同时使得它们的和最大。

将每个位置是否被选用 01 01 01 串表示,假设有 x x x 01 01 01 y y y 10 10 10 z z z 00 00 00 z z z 11 11 11 (显然 00 00 00 11 11 11 的个数是相同的),那么我们的要求是:

  • 01 01 01 11 11 11 后面必须接 01 01 01 或者 00 00 00
  • 10 10 10 11 11 11 前面必须接 10 10 10 或者 00 00 00

如果 z = 0 z = 0 z=0 ,那么要么 x = n x = n x=n ,要么 y = n y = n y=n

否则一定有解,先将 z z z 11 11 11 z − 1 z - 1 z1 00 00 00 像这样合并成一个 11 11 11 11 − 00 − 11 − 00 − 11 11-00-11-00-11 1100110011

然后在 11 11 11 前面接上所有的 10 10 10 ,后面接上所有的 01 01 01 ,最后用剩下的 00 00 00 把它们串起来。

也就是说,特判掉 z = 0 z = 0 z=0 的情况,我们一定删的数里面一定有两个数属于同一个位置,这个贪心就可以了。

#include <bits/stdc++.h>

using namespace std;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  cin >> n;
  vector<int> a(n), b(n);
  long long answer = 0, sum_a = 0, sum_b = 0;
  vector<pair<int, int>> all(n << 1);
  for (int i = 0; i < n; ++i) {
    cin >> a[i] >> b[i];
    answer += a[i] + b[i];
    sum_a += a[i];
    sum_b += b[i];
    all[i << 1] = make_pair(a[i], i);
    all[i << 1 | 1] = make_pair(b[i], i);
  }
  long long result = max(sum_a, sum_b);
  sort(all.begin(), all.end(), greater<pair<int, int>> ());
  vector<bool> visit(n);
  bool already = false;
  long long sum = 0;
  for (int i = 0; i < n; ++i) {
    if (visit[all[i].second]) {
      already = true;
    }
    sum += all[i].first;
    visit[all[i].second] = true;
  }
  if (already) {
    result = max(result, sum);
  } else {
    for (int i = n; i < n << 1; ++i) {
      if (all[n - 1].second == all[i].second) {
        result = max(result, sum + all[i].first - all[n - 2].first);
      } else {
        result = max(result, sum + all[i].first - all[n - 1].first);
      }
    }
  }
  cout << answer - result << endl;
  return 0;
}

Chords

f ( l , r ) f(l,r) f(l,r) 表示只在区间 [ l , r ] [l,r] [l,r] 内部连, l l l r r r 是连通的概率,容斥即可。

#include <bits/stdc++.h>

using namespace std;

const int md = 1e9 + 7;

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

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

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

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n, m;
  cin >> n >> m;
  vector<int> a(m), b(m);
  for (int i = 0; i < m; ++i) {
    cin >> a[i] >> b[i];
    --a[i];
    --b[i];
  }
  vector<int> ways(n + 1);
  ways[0] = 1;
  for (int i = 1; i <= n; ++i) {
    ways[i] = mul(ways[i - 1], (i << 1) - 1);
  }
  vector<vector<int>> f(n << 1, vector<int> (n << 1));
  vector<vector<int>> g(n << 1, vector<int> (n << 1));
  int answer = 0;
  for (int l = (n << 1) - 1; ~l; --l) {
    for (int r = l + 1; r < n << 1; r += 2) {
      bool flag = true;
      int inside = 0;
      for (int i = 0; i < m; ++i) {
        int type = (a[i] >= l && a[i] <= r) + (b[i] >= l && b[i] <= r);
        if (type == 1) {
          flag = false;
          break;
        }
        if (type) {
          ++inside;
        }
      }
      if (flag) {
        inside = (r - l + 1 >> 1) - inside;
        int outside = n - m - inside;
        f[l][r] = g[l][r] = ways[inside];
        for (int i = l + 1; i < r - 1; i += 2) {
          f[l][r] = sub(f[l][r], mul(f[l][i], g[i + 1][r]));
        }
        answer = add(answer, mul(f[l][r], ways[outside]));
      }
    }
  }
  cout << answer << endl;
  return 0;
}

High Elements

直接的想法是按位确定,那么需要判断一个状态是否合法。对于前缀的一个状态,需要记录当前两个序列的最大值 m a x 0 , m a x 1 max_0, max_1 max0,max1 和前缀最大值个数 c n t 0 , c n t 1 cnt_0, cnt_1 cnt0,cnt1 。对于剩下的数,我们需要安排两个序列 a 1 , a 2 , ⋯ &ThinSpace; , a x a_1, a_2, \cdots, a_x a1,a2,,ax b 1 , b 2 , ⋯ &ThinSpace; , b y b_1, b_2, \cdots, b_y b1,b2,,by ,使得:

  • m a x 0 &lt; a 1 &lt; a 2 &lt; ⋯ &lt; a x max_0 &lt; a_1 &lt; a_2 &lt; \cdots &lt; a_x max0<a1<a2<<ax
  • m a x 1 &lt; b 1 &lt; b 2 &lt; ⋯ &lt; b y max_1 &lt; b_1 &lt; b_2 &lt; \cdots &lt; b_y max1<b1<b2<<by
  • x + m a x 0 = y + m a x 1 x + max_0 = y + max_1 x+max0=y+max1
  • 剩下的数里面,所有在原序列中的前缀最大值一定在序列 a a a 或者 b b b 里面。

事实上这些条件是充要的,因为所有不是原序列前缀最大值的数,可以放在前缀最大值所在的序列后面,不产生贡献。

那么,我们可以将 a a a b b b 其中一个调整成全是原序列的前缀最大值。假设 a a a 全是原序列的前缀最大值,那么我们只需要确定 b b b ,然后把剩下的没有分配的原序列前缀最大值安排到 a a a 上面就行了。

假设剩下的数里面原序列前缀最大值个数为 q q q ,有 k k k 个被放到 b b b 里面了,那么有:

c n t 0 + q − k = c n t 1 + y cnt_0 + q - k= cnt_1 + y cnt0+qk=cnt1+y

假设 b b b 里面有 m m m 个不是原来的前缀最大值的数,那么移项变成:

2 k + m = c n t 0 − c n t 1 + q 2k + m = cnt_0 - cnt_1 + q 2k+m=cnt0cnt1+q

注意到右边是定值,所以这个问题变成了找一个上升子序列,每个位置权值是 1 1 1 或者 2 2 2 ,要凑出某个权值。

显然如果 c c c 能凑出来,那么 c − 2 c-2 c2 也能凑出来,所以分奇偶倒着维护一个类似最长上升子序列的DP就行了。

#include <bits/stdc++.h>

using namespace std;

const int inf = 0x3f3f3f3f;

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  cin >> n;
  vector<int> a(n);
  vector<bool> b(n);
  int prefix = -1, q = 0;
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
    --a[i];
    if (a[i] > prefix) {
      prefix = a[i];
      b[i] = true;
      ++q;
    }
  }
  
  vector<int> fenw_even(n, 0), fenw_odd(n, -inf);
  stack<pair<int*, int>> memory;

  auto modify = [&](vector<int> &fenw, int x, int value) {
    while (x < n) {
      if (fenw[x] < value) {
        memory.emplace(&fenw[x], fenw[x]);
        fenw[x] = value;
      }
      x |= x + 1;
    }
  };

  auto query = [&](vector<int> &fenw, int x) {
    int result = -inf;
    while (x >= 0) {
      result = max(result, fenw[x]);
      x = (x & x + 1) - 1;
    }
    return result;
  };

  vector<int> size(n);
  for (int i = n - 1; ~i; --i) {
    int even = -inf, odd = -inf;
    size[i] = memory.size();
    if (b[i]) {
      even = query(fenw_even, n - a[i] - 1) + 2;
      odd = query(fenw_odd, n - a[i] - 1) + 2;
    } else {
      even = query(fenw_odd, n - a[i] - 1) + 1;
      odd = query(fenw_even, n - a[i] - 1) + 1;
    }
    if (even >= 0) {
      modify(fenw_even, n - a[i] - 1, even);
    }
    if (odd >= 0) {
      modify(fenw_odd, n - a[i] - 1, odd);
    }
  }

  auto check = [&](int x, int need) {
    if (need < 0) {
      return false;
    }
    if (need & 1) {
      return query(fenw_odd, n - x - 1) >= need;
    } else {
      return query(fenw_even, n - x - 1) >= need;
    }
  };

  auto valid = [&](int max0, int max1, int diff, int q) {
    return check(max0, q - diff) || check(max1, q + diff);
  };

  int max0 = 0, max1 = 0;
  if (!check(0, q)) {
    puts("-1");
    return 0;
  }
  int diff = 0;
  for (int i = 0; i < n; ++i) {
    if (b[i]) {
      --q;
    }
    while (memory.size() > size[i]) {
      *memory.top().first = memory.top().second;
      memory.pop();
    }
    if (max0 <= a[i]) {
      if (valid(a[i], max1, diff + 1, q)) {
        cout << 0;
        max0 = a[i];
        ++diff;
      } else {
        cout << 1;
        if (max1 <= a[i]) {
          max1 = a[i];
          --diff;
        }
      }
    } else {
      if (valid(max0, max1, diff, q)) {
        cout << 0;
      } else {
        cout << 1;
        if (max1 <= a[i]) {
          max1 = a[i];
          --diff;
        }
      }
    }
  }
  cout << endl;

  return 0;
}

Reachable Cells

假设 n ≥ m n\ge m nm ,考虑分治,那么只需要算从一个上面的点到下面的点的贡献。

将上方第 i i i 行第 j j j 列的数记为 U ( i , j ) U(i, j) U(i,j) ,下方的记为 D ( i , j ) D(i,j) D(i,j) ,假设上方有 H U H_U HU 行,下方有 H D H_D HD 行。为了方便这里不考虑带权。

定义:

  • M e e t i n g P o i n t ( a , b ) MeetingPoint(a,b) MeetingPoint(a,b) 表示 D ( 1 , a ) D(1,a) D(1,a) D ( 1 , b ) D(1,b) D(1,b) 最早在哪行能够同时到达( a ≤ b a\le b ab)。
  • B o t h R e a c h a b l e ( a , b , l ) BothReachable(a,b,l) BothReachable(a,b,l) 表示 D ( 1 , a ) D(1,a) D(1,a) D ( 1 , b ) D(1,b) D(1,b) 在前 l l l 行同时能到达的点数( a ≤ b a\le b ab)。
  • L e f t ( i , j ) Left(i,j) Left(i,j) 表示最小的 x x x 使得 D ( 1 , x ) D(1,x) D(1,x) 可以到 D ( i , j ) D(i,j) D(i,j)
  • R i g h t ( i , j ) Right(i,j) Right(i,j) 表示最大的 x x x 使得 D ( 1 , x ) D(1,x) D(1,x) 可以到 D ( i , j ) D(i,j) D(i,j)
  • T o p ( j ) Top(j) Top(j) 表示最小的 y y y 使得 U ( y , x ) U(y,x) U(y,x) 可以到 D ( 1 , j ) D(1, j) D(1,j)
  • B o t t o m ( j ) Bottom(j) Bottom(j) 表示最大的 y y y 使得 D ( 1 , j ) D(1,j) D(1,j) 可以到 D ( y , x ) D(y,x) D(y,x)

考虑实现 M e e t i n g P o i n t MeetingPoint MeetingPoint 这个函数,那么显然有 L e f t ( i , j ) ≤ a ≤ b ≤ R i g h t ( i , j ) Left(i, j)\le a\le b\le Right(i,j) Left(i,j)abRight(i,j) ,找到满足这个的使得 i i i 最小的点 ( p , q ) (p,q) (p,q) ,分为两种情况:

  • p ≤ min ⁡ ( B o t t o m ( a ) , B o t t o m ( b ) ) p\le \min(Bottom(a), Bottom(b)) pmin(Bottom(a),Bottom(b)) 时,返回 p p p 。考虑 L e f t ( i , j ) , R i g h t ( i , j ) , B o t t o m ( a ) , B o t t o m ( b ) Left(i,j), Right(i,j), Bottom(a), Bottom(b) Left(i,j),Right(i,j),Bottom(a),Bottom(b) 构成的路径,易证。
  • p &gt; min ⁡ ( B o t t o m ( a ) , B o t t o m ( b ) ) p &gt; \min(Bottom(a), Bottom(b)) p>min(Bottom(a),Bottom(b)) 时,返回 ∞ \infty 。显然。

可以通过预处理 O ( 1 ) O(1) O(1) 查询 M e e t i n g P o i n t MeetingPoint MeetingPoint

再考虑实现 B o t h R e a c h a b l e BothReachable BothReachable 这个函数,对于 M e t t i n g P o i n t ( a , b ) &gt; l MettingPoint(a,b) &gt; l MettingPoint(a,b)>l 的情况,直接返回 0 0 0

其他情况,相当于数 L e f t ( y , x ) ≤ a ≤ b ≤ R i g h t ( y , x ) , y ≤ l Left(y,x)\le a\le b\le Right(y,x), y\le l Left(y,x)abRight(y,x),yl ( y , x ) (y,x) (y,x) 的个数。

如果没有 y ≤ l y\le l yl 这个条件,可以容斥,算:

  • 加上 L e f t ( y , x ) ≤ R i g h t ( y , x ) Left(y,x)\le Right(y,x) Left(y,x)Right(y,x) 的个数
  • 减去 L e f t ( y , x ) &gt; a Left(y,x) &gt; a Left(y,x)>a 的个数
  • 减去 R i g h t ( y , x ) &lt; b Right(y,x) &lt; b Right(y,x)<b 的个数
  • 加上 a &lt; L e f t ( y , x ) ≤ R i g h t ( y , x ) &lt; b a &lt; Left(y,x)\le Right(y,x) &lt; b a<Left(y,x)Right(y,x)<b 的个数

前三个显然加上 y ≤ l y\le l yl 这个条件也是可以完成的,对于第四个,我们注意到这些东西的 y y y 一定比 M e t t i n g P o i n t ( a , b ) MettingPoint(a,b) MettingPoint(a,b) 小,所以可以直接忽视 y ≤ l y\le l yl 的条件。

在上述函数的基础上,可以定义 R e a c h a b l e ( a ) Reachable(a) Reachable(a) 表示 D ( 1 , a ) D(1,a) D(1,a) 能到达的位置个数。

枚举 y y y ,定义 L ( x ) L(x) L(x) 表示 U ( y , x ) U(y,x) U(y,x) 能到达 U ( H u , j ) U(H_u, j) U(Hu,j) 最小的 j j j R ( x ) R(x) R(x) 表示最大的 j j j 。那么 L ( x ) , R ( x ) L(x), R(x) L(x),R(x) 都是单调不降的。问题变成:维护一个集合,支持插入 D ( 1 , j ) D(1,j) D(1,j) ,删除 D ( 1 , j ) D(1,j) D(1,j) ,询问当前集合内能到达的点个数。保证插入的 j j j 是当前最大值,删除的 j j j 是当前最小值。

定义 h a s j has_j hasj 表示 D ( 1 , j ) D(1,j) D(1,j) 能到达的,并且集合中大于 j j j 的任何位置都不能到达的位置个数。注意到删除操作是不会改变 h a s j has_j hasj 的,所以只考虑插入操作。对于 D ( 1 , j ′ ) ∈ S D(1, j&#x27;)\in S D(1,j)S ,如果存在 D ( 1 , j ′ ′ ) ∈ S ( j ′ ′ &gt; j ′ ) D(1,j&#x27;&#x27;)\in S(j&#x27;&#x27; &gt; j&#x27;) D(1,j)S(j>j) 并且 B o t t o m ( j ′ ) ≤ B o t t o m ( j ′ ′ ) Bottom(j&#x27;)\le Bottom(j&#x27;&#x27;) Bottom(j)Bottom(j) ,则 h a s j has_j hasj 不会改变。这是因为如果 j j j j ′ j&#x27; j 都能到,那么显然 j ′ ′ j&#x27;&#x27; j 也能到。所以,会改变的是一个关于 B o t t o m Bottom Bottom 的单调栈:假设是 J 1 , J 2 , ⋯ &ThinSpace; , J k J_1, J_2, \cdots, J_k J1,J2,,Jk h a s J k has_{J_k} hasJk 会减去 B o t h R e a c h a b l e ( J k , j , min ⁡ ( B o t t o m ( J k ) , B o t t o m ( j ) ) ) BothReachable(J_k, j, \min(Bottom(J_k), Bottom(j))) BothReachable(Jk,j,min(Bottom(Jk),Bottom(j))) 。而对于 p &lt; k p &lt; k p<k h a s J p has_{J_p} hasJp 会减去 D ( 1 , j ) D(1,j) D(1,j) D ( 1 , J p ) D(1, J_p) D(1,Jp) 都能到,但 D ( 1 , J q ) ( q &gt; p ) D(1, J_q)(q &gt; p) D(1,Jq)(q>p) 不能到的格子个数。这个值是 B o t h R e a c h a b l e ( J p , j , min ⁡ ( B o t t o m ( J p ) , B o t t o m ( j ) ) ) − B o t h R e a c h a b l e ( J p , j , min ⁡ ( B o t t o m ( J p + 1 , j ) ) ) BothReachable(J_p, j, \min(Bottom(J_p), Bottom(j))) - BothReachable(J_p, j, \min(Bottom(J_{p+1}, j))) BothReachable(Jp,j,min(Bottom(Jp),Bottom(j)))BothReachable(Jp,j,min(Bottom(Jp+1,j)))

。注意到当 B o t t o m ( J p ) &gt; B o t t o m ( j ) Bottom(J_p)&gt; Bottom(j) Bottom(Jp)>Bottom(j) 时, h a s q ( q &lt; p ) has_q(q &lt; p) hasq(q<p) 不变,所以可以直接用单调栈来更新。

#include <bits/stdc++.h>

using namespace std;

const int inf = 0x3f3f3f3f;

void cmax(int &x, int y) {
  if (x < y) {
    x = y;
  }
}

void cmin(int &x, int y) {
  if (x > y) {
    x = y;
  }
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  int n;
  cin >> n;
  vector<string> board(n);
  for (int i = 0; i < n; ++i) {
    cin >> board[i];
  }
  long long answer = 0;

  function<void(vector<string>)> solve = [&](vector<string> board) {
    int n = board.size(), m = board[0].size();
    
    if (n < m) {
      vector<string> rotated(m, string(n, ' '));
      for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
          rotated[j][i] = board[i][j];
        }
      }
      swap(n, m);
      board = rotated;
    }
    
    if (m == 1) {
      int sum = 0;
      for (int i = 0; i < n; ++i) {
        if (board[i][0] == '#') {
          sum = 0;
        } else {
          answer += (board[i][0] - '0') * sum;
          sum += board[i][0] - '0';
        }
      }
      return;
    }

    vector<string> u = vector<string> (board.begin(), board.begin() + (n >> 1));
    vector<string> d = vector<string> (board.begin() + (n >> 1), board.end());
    solve(u);
    solve(d);
    u.push_back(d[0]);

    int nu = n >> 1, nd = n + 1 >> 1;
    vector<vector<int>> left(nd, vector<int> (m, inf));
    vector<vector<int>> right(nd, vector<int> (m, -inf));
    vector<int> top(m);
    vector<int> bottom(m);
    for (int i = 0; i < m; ++i) {
      if (d[0][i] != '#') {
        left[0][i] = right[0][i] = i;
      }
    }
    for (int i = 0; i < nd; ++i) {
      for (int j = 0; j < m; ++j) {
        if (d[i][j] != '#') {
          if (i) {
            cmin(left[i][j], left[i - 1][j]);
            cmax(right[i][j], right[i - 1][j]);
          }
          if (j) {
            cmin(left[i][j], left[i][j - 1]);
            cmax(right[i][j], right[i][j - 1]);
          }
        }
      }
    }

    {
      vector<vector<int>> temp(nd, vector<int> (m, -inf));
      for (int i = nd - 1; ~i; --i) {
        for (int j = m - 1; ~j; --j) {
          if (d[i][j] != '#') {
            temp[i][j] = i;
            if (i + 1 < nd) {
              cmax(temp[i][j], temp[i + 1][j]);
            }
            if (j + 1 < m) {
              cmax(temp[i][j], temp[i][j + 1]);
            }
          }
        }
      }
      for (int i = 0; i < m; ++i) {
        bottom[i] = temp[0][i];
      }
    }

    {
      vector<vector<int>> temp(nu + 1, vector<int> (m, inf));
      for (int i = 0; i <= nu; ++i) {
        for (int j = 0; j < m; ++j) {
          if (u[i][j] != '#') {
            temp[i][j] = i;
            if (i) {
              cmin(temp[i][j], temp[i - 1][j]);
            }
            if (j) {
              cmin(temp[i][j], temp[i][j - 1]);
            }
          }
        }
      }
      for (int i = 0; i < m; ++i) {
        top[i] = temp[nu][i];
      }
    }

    vector<vector<int>> mp(m, vector<int> (m, inf));
    for (int i = 0; i < nd; ++i) {
      for (int j = 0; j < m; ++j) {
        if (left[i][j] <= right[i][j]) {
          cmin(mp[left[i][j]][right[i][j]], i);
        }
      }
    }
    for (int l = 0; l < m; ++l) {
      for (int r = m - 1; ~r; --r) {
        if (l) {
          cmin(mp[l][r], mp[l - 1][r]);
        }
        if (r + 1 < m) {
          cmin(mp[l][r], mp[l][r + 1]);
        }
      }
    }

    auto meeting_point = [&](int a, int b) {
      int p = mp[a][b];
      return p <= min(bottom[a], bottom[b]) ? p : inf;
    };

    vector<int> sum1(nd);
    vector<vector<int>> sum2(nd, vector<int> (m));
    vector<vector<int>> sum3(nd, vector<int> (m));
    vector<vector<int>> sum4(m, vector<int> (m));

    for (int i = 0; i < nd; ++i) {
      if (i) {
        sum1[i] = sum1[i - 1];
        for (int j = 0; j < m; ++j) {
          sum2[i][j] = sum2[i - 1][j];
          sum3[i][j] = sum3[i - 1][j];
        }
      }
      for (int j = 0; j < m; ++j) {
        if (left[i][j] <= right[i][j]) {
          sum1[i] += d[i][j] - '0';
          if (left[i][j]) {
            sum2[i][left[i][j] - 1] += d[i][j] - '0';
          }
          if (right[i][j] + 1 < m) {
            sum3[i][right[i][j] + 1] += d[i][j] - '0';
          }
          if (left[i][j] && right[i][j] + 1 < m) {
            sum4[left[i][j] - 1][right[i][j] + 1] += d[i][j] - '0';
          }
        }
      }
    }
    for (int i = 0; i < nd; ++i) {
      for (int j = m - 1; j; --j) {
        sum2[i][j - 1] += sum2[i][j];
      }
      for (int j = 1; j < m; ++j) {
        sum3[i][j] += sum3[i][j - 1];
      }
    }
    for (int l = m - 1; ~l; --l) {
      for (int r = l; r < m; ++r) {
        if (l + 1 < m) {
          sum4[l][r] += sum4[l + 1][r];
        }
        if (r) {
          sum4[l][r] += sum4[l][r - 1];
        }
        if (l + 1 < m && r) {
          sum4[l][r] -= sum4[l + 1][r - 1];
        }
      }
    }

    auto both_reachable = [&](int a, int b, int l) {
      if (meeting_point(a, b) > l) {
        return 0;
      } else {
        return sum1[l] - sum2[l][a] - sum3[l][b] + sum4[a][b];
      }
    };

    vector<int> reachable(m);
    for (int i = 0; i < m; ++i) {
      reachable[i] = both_reachable(i, i, bottom[i]);
    }

    vector<vector<int>> event(nu);
    vector<bool> ban(m);
    for (int i = 0; i < m; ++i) {
      if (top[i] >= nu) {
        ban[i] = true;
      } else {
        event[top[i]].push_back(i);
      }
    }
    vector<int> l(m), r(m);
    for (int i = m - 1; ~i; --i) {
      if (d[0][i] == '#') {
        l[i] = inf;
        r[i] = -inf;
      } else {
        l[i] = i;
        r[i] = max(i, i + 1 < m ? r[i + 1] : -inf);
      }
    }
    for (int row = nu - 1; ~row; --row) {
      for (int i = m - 1; ~i; --i) {
        if (u[row][i] == '#') {
          l[i] = inf;
          r[i] = -inf;
        } else if (i + 1 < m) {
          cmin(l[i], l[i + 1]);
          cmax(r[i], r[i + 1]);
        }
      }
      vector<int> has(m);
      vector<int> st(m);
      int sum = 0, stl = 0, str = 0, myl = 0, myr = -1;
      
      auto insert = [&](int x) {
        if (!ban[x]) {
          if (stl < str) {
            sum -= has[st[str - 1]];
            has[st[str - 1]] -= both_reachable(st[str - 1], x, min(bottom[st[str - 1]], bottom[x]));
            sum += has[st[str - 1]];
            if (bottom[st[str - 1]] <= bottom[x]) {
              --str;
              while (stl < str) {
                sum -= has[st[str - 1]];
                has[st[str - 1]] -= both_reachable(st[str - 1], x, min(bottom[st[str - 1]], bottom[x])) - both_reachable(st[str - 1], x, min(bottom[st[str]], bottom[x]));
                sum += has[st[str - 1]];
                if (bottom[st[str - 1]] <= bottom[x]) {
                  --str;
                } else {
                  break;
                }
              }
            }
          }
          st[str++] = x;
          has[x] = reachable[x];
          sum += has[x];
        }
      };

      auto erase = [&](int x) {
        if (!ban[x]) {
          sum -= has[x];
          if (stl < str && st[stl] == x) {
            ++stl;
          }
        }
      };

      for (int i = 0; i < m; ++i) {
        if (l[i] <= r[i]) {
          while (myr < r[i]) {
            insert(++myr);
          }
          while (myl < l[i]) {
            erase(myl++);
          }
          answer += (u[row][i] - '0') * sum;
        }
      }
      for (auto p : event[row]) {
        ban[p] = true;
      }
    }
  };

  solve(board);
  cout << answer << endl;
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值