接着剤の精進日記

競プロでの精進や研究に関係したことを書いていきます。

AtCoder Beginner Contest 301(ABC301)

atcoder.jp

A - Overall Winner

TA の個数を数える
個数が同じ場合、最後に数えたものが負け

提出コード

void solve() {
    int N;
    string S;
    cin >> N >> S;
    bool f = false;
    int a = 0, t = 0;
    REP(i, N) {
        if (S[i] == 'T') {
            t++;
            f = true;
        }
        else {
            a++;
            f = false;
        }
    }
    if (a > t) cout << "A" << endl;
    else if (a < t) cout << "T" << endl;
    else {
        if (f) cout << "A" << endl;
        else cout << "T" << endl;
    }
}

B - Fill the Gaps

問題文の条件のとおりに出力をする

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    cout << A[0] << " ";
    REP(i, 1, N) {
        if (abs(A[i] - A[i - 1]) == 1) cout << A[i] << " ";
        else if (A[i] > A[i - 1]) {
            REP(j, A[i - 1] + 1, A[i] + 1) cout << j << " ";
        }
        else if (A[i] < A[i - 1]) {
            for (int j = A[i - 1] - 1; j >= A[i]; j--)
                cout << j << " ";
        }
    }
    cout << endl;
}

C - AtCoder Cards

各文字ごとに文字数を数え、STで文字数が同じかどうかを比較していく
このとき @a, t, c, o, d, e, rに置き換えられるので足りない分は置き換えてしまっていい
STで文字数を同じにできない文字がある場合はNo、それ以外はYesとなる

提出コード

void solve() {
    string S, T;
    cin >> S >> T;
    vector<int> s(27), t(27);
    for (char c : S) {
        if (c == '@') s[26]++;
        else s[c - 'a']++;
    }
    for (char c : T) {
        if (c == '@') t[26]++;
        else t[c - 'a']++;
    }
    set<char> st = {'a', 't', 'c', 'o', 'd', 'e', 'r'};
    REP(i, 26) {
        if (s[i] == t[i]) continue;
        else if (!st.count(char(i + 'a'))) {
            cout << "No" << endl;
            return;
        }
        else if (s[i] > t[i]) {
            if (s[i] - t[i] > t[26]) {
                cout << "No" << endl;
                return;
            }
            else {
                t[26] -= s[i] - t[i];
            }
        }
        else if (s[i] < t[i]) {
            if (t[i] - s[i] > s[26]) {
                cout << "No" << endl;
                return;
            }
            else {
                s[26] -= t[i] - s[i];
            }
        }
    }
    cout << "Yes" << endl;
}

D - Bitmask

?0とした場合の2進数整数を予め求めておく
その後、上位bitから順に見ていき?1としたときの2進数整数が $ N $ 以下の場合、そのbitは1としてしまって良い

提出コード

void solve() {
    string S;
    ll N;
    cin >> S >> N;
    ll cur = 0;
    reverse(ALL(S));
    REP(i, S.size()) if (S[i] == '1') cur += 1LL << i;
    if (cur > N) {
        cout << -1 << endl;
        return;
    }
    for (int i = (int)S.size() - 1; i >= 0; i--)
        if (S[i] == '?' and cur + (1LL << i) <= N) cur += 1LL << i;
    cout << cur << endl;
}

E - Pac-Takahashi

S, G, o のみに注目すると頂点数は高々20となり巡回セールスマン問題のようにbit DPで求めることができる
そのため、各頂点間の距離を予めBFSなどで求めておくことで以下のDPで答えを求めることができる
$ dp[i][j] := $ 最後に訪れた頂点が $ i $ で、訪れた頂点集合が $ j $ としたときの移動回数の最小
求めたい答えは、最後にGを訪れたときに移動回数が $ T $ 以下であるときの頂点集合に含まれる oの数の最大であるので条件を満たすもののうち、頂点集合に含まれる oの数の最大を求めればいい

提出コード

void solve() {
    int H, W, T;
    cin >> H >> W >> T;
    vector<string> s(H);
    REP(i, H) cin >> s[i];
    vector<pair<int, int>> snack;
    int sh, sw, gh, gw;
    REP(i, H) REP(j, W) {
        if (s[i][j] == 'S') sh = i, sw = j;
        else if (s[i][j] == 'G') gh = i, gw = j;
    }
    snack.emplace_back(sh, sw);
    snack.emplace_back(gh, gw);
    REP(i, H) REP(j, W) {
        if (s[i][j] == 'o') snack.emplace_back(i, j);
    }
    int n = snack.size();
    vector<vector<int>> dist(n, vector<int>(n, INF));
    REP(i, n) {
        auto [h, w] = snack[i];
        vector<vector<int>> d(H, vector<int>(W, INF));
        d[h][w] = 0;
        queue<pair<int, int>> q;
        q.emplace(h, w);
        while (!q.empty()) {
            auto [cur_h, cur_w] = q.front();
            q.pop();
            REP(dir, 4) {
                int nh = cur_h + dx[dir];
                int nw = cur_w + dy[dir];
                if (nh < 0 || nh >= H || nw < 0 || nw >= W) continue;
                if (s[nh][nw] == '#') continue;
                if (d[nh][nw] < INF) continue;
                d[nh][nw] = d[cur_h][cur_w] + 1;
                q.emplace(nh, nw);
            }
        }
        REP(j, n) dist[i][j] = d[snack[j].first][snack[j].second];
    }
    vector dp(n, vector<int>(1 << n, INF));
    dp[0][0] = 0;
    dp[0][1] = 0;  // s = 0, g=1
    REP(i, 1, (1 << n) + 1) {
        REP(j, n) if (i >> j & 1) {
            REP(k, n) {
                int cost = dp[j][i ^ (1 << j)] + dist[j][k];
                chmin(dp[k][i], cost);
            }
        }
    }
    int ma = -1;
    REP(i, 1 << n) if ((i & 1) and (i >> 1 & 1) and dp[1][i] <= T) { chmax(ma, __builtin_popcount(i ^ 1 ^ 2)); }
    cout << ma << endl;
}

AtCoder Beginner Contest 298(ABC298)

atcoder.jp

A - Job Interview

o があるかどうかとxがあるかどうかをそれぞれ確認する

提出コード

void solve() {
    int N;
    string S;
    cin >> N >> S;
    bool ok1 = false;
    bool ok2 = true;
    REP(i, N) {
        if (S[i] == 'o') ok1 |= true;
        if (S[i] == 'x') ok2 = false;
    }
    cout << (ok1 and ok2 ? "Yes" : "No") << endl;
}

B - Coloring Matrix

4回回転させれば元に戻るので全探索する

提出コード

void solve() {
    int N;
    cin >> N;
    vector<vector<int>> A(N, vector<int>(N));
    auto B = A;
    REP(i, N) REP(j, N) cin >> A[i][j];
    REP(i, N) REP(j, N) cin >> B[i][j];
    REP(_, 4) {
        bool ok = true;
        REP(i, N) REP(j, N) {
            if (A[i][j] == 1 and B[i][j] == 0) ok = false;
        }
        if (ok) {
            cout << "Yes" << endl;
            return;
        }
        auto a = A;
        REP(i, N) REP(j, N) { A[i][j] = a[N - j - 1][i]; }
    }
    cout << "No" << endl;
}

C - Cards Query Problem

箱は重複を許すmultiset、数は重複なしのsetで管理する

提出コード

void solve() {
    int N, Q;
    cin >> N >> Q;
    vector<multiset<int>> hako(202020);
    vector<set<int>> number(202020);
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int i, j;
            cin >> i >> j;
            hako[j].emplace(i);
            number[i].emplace(j);
        }
        else if (q == 2) {
            int i;
            cin >> i;
            for (auto x : hako[i]) {
                cout << x << " ";
            }
            cout << endl;
        }
        else {
            int i;
            cin >> i;
            for (auto x : number[i]) {
                cout << x << " ";
            }
            cout << endl;
        }
    }
}

D - Writing a Numeral

先頭の要素取得と削除が可能なdequeで管理しながら追加、削除で差分計算をしていく
よく考えるとqueueでも良かった

提出コード

void solve() {
    int Q;
    cin >> Q;
    mint sum = 1;
    deque<int> dq;
    dq.push_front(1);
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int x;
            cin >> x;
            dq.push_back(x);
            sum = sum * 10 + x;
        }
        else if (q == 2) {
            int d = dq.size();
            int x = dq.front();
            dq.pop_front();
            sum = sum - modpow(mint(10), d - 1) * x;
        }
        else cout << sum << endl;
    }
}

E - Unfair Sugoroku

青木くんと高橋くんそれぞれ独立に考えていいので、以下のDPを求める
$ dp[i][j] := i $ 回目に地点 $ x $ にいる確率
高橋くんが $ i $ 回目に勝つ確率は、高橋くんが$ i $ 回目に地点 $ N $ にいる確率と青木くんが $ i - 1 $ 回目に地点 $ N $ 以外にいる確率との積になるのでその総和を求めればいい

提出コード

void solve() {
    int N, A, B, P, Q;
    cin >> N >> A >> B >> P >> Q;
    vector<vector<mint>> aoki(N + 1, vector<mint>(N + 1, 0));
    aoki[0][B] = 1;
    REP(k, N) {
        REP(i, N) REP(j, 1, Q + 1) {
            int to = (i + j > N ? N : i + j);
            aoki[k + 1][to] += aoki[k][i] / Q;
        }
    }
    vector<vector<mint>> takahashi(N + 1, vector<mint>(N + 1, 0));
    takahashi[0][A] = 1;
    REP(k, N) {
        REP(i, N) REP(j, 1, P + 1) {
            int to = (i + j > N ? N : i + j);
            takahashi[k + 1][to] += takahashi[k][i] / P;
        }
    }
    mint ans = 0;
    REP(i, 1, N + 1) {
        if (takahashi[i][N] == 0) continue;
        mint p = 0;
        REP(j, N) p += aoki[i - 1][j];
        ans += p * takahashi[i][N];
    }
    cout << ans << endl;
}

F - Rook Score

座圧をして平面走査を行う
$ r $ 行目の総和をセグメント木で管理し、その最大値を求める
$ w $ 列目の総和をそれぞれ求めながら重複する$ r $行目の差分計算を行い最大値を更新していく

提出コード

void solve() {
    int N;
    cin >> N;
    Compress<ll> cmp_h;
    vector<ll> h(N), w(N), x(N);
    REP(i, N) {
        cin >> h[i] >> w[i] >> x[i];
        cmp_h.add(h[i]);
    }
    cmp_h.build();
    int sz = cmp_h.size() + 1;
    map<ll, vector<LP>> mpw;
    vector<ll> h_val(sz + 1);
    REP(i, N) {
        mpw[w[i]].emplace_back(cmp_h.get(h[i]), x[i]);
        h_val[cmp_h.get(h[i])] += x[i];
    }
    ll ans = 0;
    segtree<ll, op, e> seg(h_val);
    for (auto [k, v] : mpw) {
        ll sum = 0;
        for (auto [h_idx, x] : v) {
            seg.set(h_idx, seg.get(h_idx) - x);
            sum += x;
        }
        chmax(ans, sum + seg.all_prod());
        for (auto [h_idx, x] : v) {
            seg.set(h_idx, seg.get(h_idx) + x);
        }
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 297(ABC297)

atcoder.jp

A - Double Click

$ T _ {i - 1} - T _ i \leq D $ を満たす最小の $ T _ {i + 1} $ を出力する

提出コード

void solve() {
    int N, D;
    cin >> N >> D;
    vector<int> T(N);
    REP(i, N) cin >> T[i];
    REP(i, N - 1) if (T[i + 1] - T[i] <= D) {
        cout << T[i + 1] << endl;
        return;
    }
    cout << -1 << endl;
}

B - chess960

問題文の条件を満たすがどうかを全探索で確認する

提出コード

void solve() {
    string S;
    cin >> S;
    int N = S.size();
    bool ok1 = false;
    bool ok2 = false;
    REP(x, N) REP(y, x + 1, N) {
        if (S[x] == S[y] and S[x] == 'B' and x % 2 != y % 2) ok1 = true;
    }
    REP(x, N) {
        REP(y, x + 2, N) {
            REP(z, x + 1, y) {
                if (S[x] == S[y] and S[x] == 'R' and S[z] == 'K') {
                    ok2 = true;
                }
            }
        }
    }
    cout << (ok1 and ok2 ? "Yes" : "No") << endl;
}

C - PC on the Table

左端から条件を満たすものに操作を行っていく

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    vector<string> S(H);
    REP(i, H) cin >> S[i];
    REP(i, H) {
        REP(j, W - 1) if (S[i][j] == S[i][j + 1] and S[i][j] == 'T') {
            S[i][j] = 'P';
            S[i][j + 1] = 'C';
        }
    }
    REP(i, H) cout << S[i] << endl;
}

D - Count Subtractions

ユークリッド互除法を行いながらその回数を計算する

提出コード

void solve() {
    ll A, B;
    cin >> A >> B;
    ll ans = 0;
    auto f = [&](auto &&self, ll x, ll y) -> ll {
        if (y) {
            ans += x / y;
            return self(self, y, x % y);
        }
        else {
            return x;
        }
    };
    f(f, A, B);
    cout << ans - 1 << endl;
}

E - 2xN Grid

priority_queue で操作によって得られる金額の最小値の候補を管理する
各操作では現在の最小値に対して $ A _ i $ を足した値を追加していく
このときすでにある値の場合は操作を行わないものとする

提出コード

void solve() {
    int N, K;
    cin >> N >> K;
    vector<ll> A(N);
    priority_queue<ll, vector<ll>, greater<ll>> pq;
    set<ll> st;
    REP(i, N) {
        cin >> A[i];
        st.insert(A[i]);
        pq.emplace(A[i]);
    }
    REP(_, K + 100) {
        if (pq.empty()) break;
        ll mi = pq.top();
        pq.pop();
        REP(i, N) {
            if (st.count(A[i] + mi)) continue;
            st.insert(A[i] + mi);
            pq.emplace(A[i] + mi);
        }
    }
    int k = 0;
    for (auto x : st) {
        k++;
        if (k == K) {
            cout << x << endl;
            break;
        }
    }
}

F - Minimum Bounding Box 2

縦 $ h $ 、横 $ w $ のサイズの長方形領域への $ K $ 個のマスの置き方の個数を求める
個数が求められると、この長方形領域は全部で $ (H - h + 1)(W - w + 1) $ 個あり、そのサイズは $ hw $ なのでその積がスコアとなる
よって長方形領域の選び方を全探索し、その総和を 縦が $ H $ 、横が $ W $ の長方形領域への $ K $ 個のマスの置き方の個数で割ったものが答え
各長方形領域の置き方の個数は包除原理で求めることができる
参考:D - AtCoder社の冬

提出コード

void solve() {
    ll H, W, K;
    cin >> H >> W >> K;
    mint ans = 0;
    bc.init(2020202);
    REP(h, 1, H + 1) REP(w, 1, W + 1) {
        mint sum = 0;
        REP(i, 1 << 4) {
            ll x = h;
            ll y = w;
            if (i >> 0 & 1) x--;
            if (i >> 1 & 1) x--;
            if (i >> 2 & 1) y--;
            if (i >> 3 & 1) y--;
            if (x < 0 or y < 0) continue;
            if (__builtin_popcount(i) & 1) sum -= bc.nCr(x * y, K);
            else sum += bc.nCr(x * y, K);
        }
        sum *= (h * w) * (H - h + 1) * (W - w + 1);
        ans += sum;
    }
    ans /= bc.nCr(H * W, K);
    cout << ans << endl;
}

AtCoder Beginner Contest 294(ABC294)

atcoder.jp

A - Filter

偶数の入力のみ出力

提出コード

void solve() {
    int N;
    cin >> N;
    REP(i, N) {
        int a;
        cin >> a;
        if (a % 2 == 0) cout << a << " ";
    }
    cout << endl;
}

B - ASCII Art

問題文のとおりに数値を文字列に変換する

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    REP(i, H) {
        REP(j, W) {
            int a;
            cin >> a;
            if (a == 0) cout << ".";
            else cout << char('A' + a - 1);
        }
        cout << endl;
    }
}

C - Merge Sequences

座圧と同じ操作をする
$ C_k = A_i $、$ C_k = B_j $ を満たす $ k $ を二分探索で求める

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    vector<int> C;
    REP(i, N) {
        cin >> A[i];
        C.emplace_back(A[i]);
    }
    REP(i, M) {
        cin >> B[i];
        C.emplace_back(B[i]);
    }
    sort(ALL(C));
    REP(i, N) {
        int idx = lower_bound(ALL(C), A[i]) - C.begin();
        cout << idx + 1 << " ";
    }
    cout << endl;
    REP(i, M) {
        int idx = lower_bound(ALL(C), B[i]) - C.begin();
        cout << idx + 1 << " ";
    }
    cout << endl;
}

D - Bank

まだ呼ばれていない番号の集合とすでに受付に呼ばれているが受付に行っていない人の集合それぞれをsetで管理をする

提出コード

void solve() {
    int N, Q;
    cin >> N >> Q;
    set<int> st1, st2;
    REP(i, N) st1.emplace(i);
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int x = *st1.begin();
            st1.erase(x);
            st2.emplace(x);
        }
        else if (q == 2) {
            int x;
            cin >> x;
            st2.erase(x - 1);
        }
        else {
            cout << *st2.begin() + 1 << endl;
        }
    }
}

E - 2xN Grid

要素ごとにRangeSetで区間を管理する
予め $ A $ の要素を区間に追加しておく
その後 $ B $ の要素を区間に追加する
$ [l, r) $ の区間を追加したときに追加される区間の増分を $ x $ とすると、その区間には元々 $ r - l - x $ 個の要素があるのでこの総和を求めればいい

提出コード

void solve() {
    ll L, N1, N2;
    cin >> L >> N1 >> N2;
    map<ll, vector<pair<ll, ll>>> mp1, mp2;
    ll sum = 0;
    REP(i, N1) {
        ll v, l;
        cin >> v >> l;
        mp1[v].emplace_back(sum, sum + l);
        sum += l;
    }
    sum = 0;
    REP(i, N2) {
        ll v, l;
        cin >> v >> l;
        mp2[v].emplace_back(sum, sum + l);
        sum += l;
    }
    ll ans = 0;
    for (auto [k, v] : mp1) {
        RangeSet<ll> rst;
        for (auto [l, r] : v) {
            rst.insert(l, r);
        }
        if (mp2.count(k)) {
            for (auto [l, r] : mp2[k]) {
                ll x = rst.insert(l, r);
                ans += r - l - x;
            }
        }
    }
    cout << ans << endl;
}

G - Distance Queries on a Tree

クエリが木上のパスではなく、列であったとすると、1点変更、区間加算ができるセグ木を使用することで求めることができる
木に対してはHL分解を行うことで列と同様にできるのでこれをセグ木を使って求めればいい

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> v(N - 1), u(N - 1), w(N - 1);
    HLD hld(N);
    REP(i, N - 1) {
        cin >> v[i] >> u[i] >> w[i];
        --v[i], --u[i];
        hld.add_edge(v[i], u[i]);
    }
    hld.build();
    SegTree<ll> seg(
        N, [](ll a, ll b) { return a + b; }, 0);
    REP(i, N - 1) { seg.set(max(hld.vid[v[i]], hld.vid[u[i]]), w[i]); }
    seg.build();
    int Q;
    cin >> Q;
    while (Q--) {
        int q;
        cin >> q;
        if (q == 1) {
            int i;
            ll w;
            cin >> i >> w;
            --i;
            seg.update(max(hld.vid[v[i]], hld.vid[u[i]]), w);
        }
        else {
            int uu, vv;
            cin >> uu >> vv;
            --uu, --vv;
            if (uu > vv) swap(uu, vv);
            ll sum = 0;
            for (auto [l, r] : hld.for_each_edge(uu, vv)) {
                sum += seg.get(l, r);
            }
            cout << sum << endl;
        }
    }
}

AtCoder Beginner Contest 293(ABC293)

atcoder.jp

A - Swap Odd and Even

問題文の通りにswapしたものを出力

提出コード

void solve() {
    string S;
    cin >> S;
    int n = S.size();
    REP(i, n / 2) swap(S[2 * i], S[2 * i + 1]);
    cout << S << endl;
}

B - Call the ID Number

シミュレートを行い、呼ばれなかった人の番号を出力

提出コード

void solve() {
    int N;
    cin >> N;
    vector<int> A(N);
    vector<int> used(N);
    REP(i, N) {
        cin >> A[i];
        --A[i];
        if (!used[i]) used[A[i]] = 1;
    }
    vector<int> ans;
    REP(i, N) if (!used[i]) ans.emplace_back(i + 1);
    cout << ans.size() << endl;
    for (auto x : ans)
        cout << x << " ";
    cout << endl;
}

C - Make Takahashi Happy

通ったマスの整数をsetなどで管理をしながら実際にDFSなどで移動経路を全探索する

提出コード

void solve() {
    int H, W;
    cin >> H >> W;
    vector A(H, vector(W, 0));
    REP(i, H) REP(j, W) cin >> A[i][j];
    int ans = 0;
    auto dfs = [&](auto &&self, int h, int w, set<int> st) {
        if (h == H - 1 and w == W - 1) {
            ans++;
            return;
        }
        REP(d, 2) {
            int nh = h + dx[d];
            int nw = w + dy[d];
            if (nh < 0 or nh >= H or nw < 0 or nw >= W) continue;
            if (st.count(A[nh][nw])) continue;
            auto nxt_st = st;
            nxt_st.emplace(A[nh][nw]);
            self(self, nh, nw, nxt_st);
        }
    };
    set<int> st;
    st.emplace(A[0][0]);
    dfs(dfs, 0, 0, st);
    cout << ans << endl;
}

D - Tying Rope

頂点を2倍にしてUnionFindで集合を管理する
制約からある集合について辺の数と頂点の個数が一致しているときサイクルとなるので各集合ごとにこれを確認する
頂点2倍にしなくてもいいらしい

提出コード

void solve() {
    int N, M;
    cin >> N >> M;
    UnionFind uf(2 * N);
    REP(i, N) uf.unite(i, N + i);
    vector<int> a(M), c(M);
    vector<char> b(M), d(M);
    REP(i, M) {
        cin >> a[i] >> b[i] >> c[i] >> d[i];
        --a[i], --c[i];
        int na = (b[i] == 'R' ? a[i] : N + a[i]);
        int nc = (d[i] == 'R' ? c[i] : N + c[i]);
        uf.unite(na, nc);
    }
    set<int> root;
    vector<int> edge(2 * N);
    REP(i, N) {
        root.emplace(uf.root(i));
        edge[uf.root(i)]++;
    }
    REP(i, M) edge[uf.root(a[i])]++;
    int X = 0, Y = root.size();
    for (auto x : root) {
        if (edge[x] == uf.size(x)) {
            X++;
            Y--;
        }
    }
    cout << X << " " << Y << endl;
}

E - Geometric Progression

求める答えは等比数列の和となる
等比数列の和を $ f(A, X, M) $ とすると $ X = 0 $ のとき $ f(A,0,M) = 0 $ となる
$ X $ が奇数のとき、$ f(A, X, M) = 1 + Af(A,X-1,M) $ となる
また、$ X $ が偶数のとき $ f(A,2n, M) = f(A,n,M) + A^n f(A,n,M) $ となるので再帰関数と繰り返し二乗法を用いて求めることができる

提出コード

void solve() {
    ll A, X, M;
    cin >> A >> X >> M;
    cout << sum(1, A, X, M) << endl;
}

G - Triple Index

ある区間 $ [l, r] $ の答えがわかっているとする
その時、区間を $ [ l - 1, r ] $ もしくは $ [l, r + 1] $ にしたときに追加される整数 $ x $ がもともとの区間に含まれる個数を $ cnt $ とするとその答えの差分は $ _{cnt +1 } C _3 - _{cnt} C _3 $ となる
同様に区間を$ [l+1, r] $ もしくは $ [l, r-1] $ としたときの答えも差分計算できる
よって上記をMo’s algorithmを用いることで求めることができる

提出コード

void solve() {
    int N, Q;
    cin >> N >> Q;
    vector<int> A(N);
    REP(i, N) cin >> A[i];
    vector<ll> ans(Q);
    vector<ll> cnt(202020);
    ll num = 0;
    auto add = [&](int idx) {
        num -= cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
        cnt[A[idx]]++;
        num += cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
    };
    auto erase = [&](int idx) {
        num -= cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
        cnt[A[idx]]--;
        num += cnt[A[idx]] * (cnt[A[idx]] - 1) * (cnt[A[idx]] - 2) / 6;
    };
    auto out = [&](int idx) { ans[idx] = num; };
    Mo mo(N);
    REP(i, Q) {
        int l, r;
        cin >> l >> r;
        mo.add(--l, r);
    }
    mo.build(add, erase, out);
    for (auto x : ans)
        cout << x << endl;
}