G - AtCoder Office

onlyblues發表於2024-08-10

G - AtCoder Office

Problem Statement

$N$ people work at the AtCoder office.

The office keeps records of entries and exits, and there have been $M$ entries and exits since the records began.

The $i$-th $(1\leq i\leq M)$ record is represented by a pair of integers $(T_i, P_i)$, indicating that at time $T_i$, the $P_i$-th person either entered the office if they were outside, or exited the office if they were inside.

It is known that all people were outside the office at the beginning of the records, and they are outside now.

Answer $Q$ queries in the following format.

For the $i$-th $(1\leq i\leq Q)$ query, you are given a pair of integers $(A_i, B_i)$. Find the total length of the periods during which both the $A_i$-th and $B_i$-th persons were inside the office since the records began.

Constraints

  • $2\leq N\leq2\times10^5$
  • $2\leq M\leq2\times10^5$
  • $1\leq T_1\lt T_2\lt\dotsb\lt T_M\leq10^9$
  • $1\leq P_i\leq N\ (1\leq i\leq M)$
  • For every $1\leq p\leq N$, the number of indices $i$ such that $P_i=p$ is even.
  • $1\leq Q\leq2\times10^5$
  • $1\leq A_i\lt B_i\leq N\ (1\leq i\leq Q)$
  • All inputs are integers.

Input

The input is given from Standard Input in the following format:

$N$ $M$
$T_1$ $P_1$
$T_2$ $P_2$
$\vdots$
$T_M$ $P_M$
$Q$
$A_1$ $B_1$
$A_2$ $B_2$
$\vdots$
$A_Q$ $B_Q$

Output

Print $Q$ lines. The $i$-th line $(1\leq i\leq Q)$ should contain the answer to the $i$-th query.


Sample Input 1

3 8
10 1
20 2
30 1
40 3
60 3
70 1
80 2
90 1
3
1 2
1 3
2 3

Sample Output 1

20
0
20

The following diagram shows the time each of the three people spent inside the office.

G - AtCoder Office

The answers to each query are as follows:

  • The 1st and 2nd persons were both inside the office from time $20$ to time $30$ and from time $70$ to time $80$. The lengths of these two periods are both $10$, so print the total, which is $20$.
  • The 1st and 3rd persons were never inside the office at the same time, so print $0$.
  • The 2nd and 3rd persons were both inside the office from time $40$ to time $60$. The length of this period is $20$, so print $20$.

Sample Input 2

10 20
10257 9
10490 4
19335 1
25893 5
32538 9
33433 3
38522 9
40629 9
42896 5
52106 1
53024 3
55610 5
56721 9
58286 9
63128 3
70513 3
70977 4
74936 5
79883 9
95116 9
7
1 3
3 9
1 9
4 9
1 5
5 9
3 5

Sample Output 2

18673
2107
15310
25720
17003
10317
16848

解題思路

  題意大概是每個人有若干個互不相交的區間,每次詢問兩人交集的長度是多少。看到時限 $5$ 秒一般時間複雜度都是帶根號的,因此容易想到分塊或根號分治的做法。

  由於所有人的區間總數是恆定的,因此可以考慮根據每個人的區間數量進行分類。設閾值 $M = O(\sqrt{m})$,那麼區間數量超過 $M$ 的人數大概就是 $O(\frac{m}{M}) = O(\sqrt{m})$ 級別。對於每個區間數量超過 $M$ 的人,我們可以透過遍歷 $m$ 條記錄預處理出該人與其他人的交集長度,時間複雜度為 $O((n+m) \sqrt{m})$。因此在詢問時,如果兩人的區間數量都不超過 $M$,直接暴力列舉區間求交集即可,時間複雜度為 $O(\sqrt{m})$,否則直接查表。

  思想比較簡單,但實現就相對困難。下面先給出如何透過 $O(m)$ 的方法求出第 $i$ 個人與其他人的交集。由於 $m$ 條記錄對應的時間沒用重複且依次遞增,這相當於進行了離散化。定義 $\mathrm{last}_j$ 表示第 $j$ 個人上一條記錄的編號,如果上條記錄對應區間右端點則設為 $0$。定義字首和 $s_j$ 表示第 $i$ 個人的前 $j$ 條記錄中區間的總長度,轉移方程為 $\displaylines{\begin{cases} s_j = s_{j-1} + t_j - t_{j-1}, &\mathrm{last}_i \ne 0 \\ s_j = s_{j-1}, &\mathrm{last}_i = 0 \end{cases}}$。因此當遍歷到第 $j$ 條記錄時,如果對應的是第 $p_j$ 個人的右端點,那麼第 $i$ 個人與第 $p_j$ 個的人的交集長度就是 $s_{j} - s_{\mathrm{last}_{p_j}}$,將該結果累加到兩人的總交集長度中。

  剩下的就是如何求出兩個區間數量不超過 $M$ 的人的交集長度。每個人的區間按左端點排序,並分別維護一個指標,假設一個人當前的區間是 $[l_i, r_i]$,另外一個人是 $[l_j, r_j]$,那麼這兩個區間的交集為 $\max\{0, \, \min\{ r_i, r_j \} - \max\{ l_i, l_j \} \}$。然後如果 $r_i < r_j$,則讓 $i$ 加 $1$ 移到下個區間,否則讓 $j$ 加 $1$。

  AC 程式碼如下,時間複雜度為 $O((n+m+q) \sqrt{m})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5, M = 1000;

array<int, 2> a[N];
vector<int> g[N];
int s[N], c[N], last[N];
map<array<int, 2>, int> mp;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, k;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> a[i][0] >> a[i][1];
        g[a[i][1]].push_back(a[i][0]);
    }
    for (int i = 1; i <= n; i++) {
        if (g[i].size() < M) continue;
        memset(c, 0, n + 1 << 2);
        for (int j = 1; j <= m; j++) {
            s[j] = s[j - 1] + (a[j][0] - a[j - 1][0]) * !!last[i];
            if (a[j][1] != i && last[a[j][1]]) c[a[j][1]] += s[j] - s[last[a[j][1]]];
            last[a[j][1]] = last[a[j][1]] ? 0 : j;
        }
        for (int j = 1; j <= n; j++) {
            if (j < i) mp[{j, i}] = c[j];
            else if (j > i) mp[{i, j}] = c[j];
        }
    }
    cin >> k;
    while (k--) {
        int x, y;
        cin >> x >> y;
        if (g[x].size() < M && g[y].size() < M) {
            int ret = 0;
            for (int i = 0, j = 0; i < g[x].size() && j < g[y].size(); ) {
                ret += max(0, min(g[x][i + 1], g[y][j + 1]) - max(g[x][i], g[y][j]));
                if (g[x][i + 1] < g[y][j + 1]) i += 2;
                else j += 2;
            }
            cout << ret << '\n';
        }
        else {
            cout << mp[{x, y}] << '\n';
        }
    }
    
    return 0;
}

參考資料

  Editorial - Toyota Programming Contest 2024#8(AtCoder Beginner Contest 365):https://atcoder.jp/contests/abc365/editorial/10610

相關文章