Hướng dẫn giải của Bedao Regular Contest 11 - EVENSUM


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Tác giả: bedao

Xét trên Segment Tree, với mỗi nút id, quản lý đoạn ~[l,r]~ ta lưu ~6~ thông tin sau:

  • left_odd: Số lượng dãy con khác rỗng ~[l,R] (R <= r)~ có tổng là số lẻ
  • left_even: Số lượng dãy con khác rỗng ~[l,R] (R <= r)~ có tổng là số chẵn
  • right_odd: Số lượng dãy con khác rỗng ~[L,r] (L >= l)~ có tổng là số lẻ
  • right_even: Số lượng dãy con khác rỗng ~[L,r] (L >= l)~ có tổng là số chẵn
  • even_total: Tổng số lượng dãy con có tổng chẵn trong đoạn ~[l,r]~
  • sum: Tổng của đoạn ~[l,r]~

Khi thực hiện thao tác kết hợp 2 nút của cây phân đoạn. Gọi par là nút cha, left là nút con trái và right là nút con phải thì:

par.sum = left.sum + right.sum;
  • Nếu left.sum là số lẻ:
par.left_odd = left.left_odd + right.left_even
par.left_even = left.left_even + right.left_odd
  • Ngược lại thì:
par.left_odd = left.left_odd + right.left_odd
par.left_even = left.left_even + right.left_even
  • Nếu right.sum là số lẻ:
par.right_odd = right.right_odd + left.right_even
par.right_even = right.right_even + left.right_odd
  • Ngược lại thì:
par.right_odd = right.right_odd + left.right_odd
par.right_even = right.right_even + left.right_even
  • Cuối cùng ta có:
par.even_total = left.even_total + right.even_total + left.right_even * right.left_even + left.right_odd * right.left_odd

Code mẫu

/*#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("avx,avx2,fma")*/

#include <bits/stdc++.h>

#define for1(i,a,b) for (int i = a; i <= b; i++)
#define for2(i,a,b) for (int i = a; i >= b; i--)
#define int long long

#define sz(a) (int)a.size()
#define pii pair<int,int>
#define pb push_back

/*
__builtin_popcountll(x) : Number of 1-bit
__builtin_ctzll(x) : Number of trailing 0
*/

const long double PI = 3.1415926535897932384626433832795;
const int INF = 1000000000000000000;
const int MOD = 1000000007;
const int MOD2 = 1000000009;
const long double EPS = 1e-6;

using namespace std;

const int N = 1e5 + 5;

struct Node{
    int ans, sum;
    int podd, peven, sodd, seven;
} ST[N * 4];
int n, q, a[N];

Node merge(Node& l, Node& r) {
    Node res;
    res.sum = (l.sum + r.sum) & 1;
    res.ans = l.ans + r.ans + l.seven * r.peven + l.sodd * r.podd;

    res.podd = l.podd + (l.sum ? r.peven : r.podd);
    res.peven = l.peven + (l.sum ? r.podd : r.peven);
    res.sodd = r.sodd + (r.sum ? l.seven : l.sodd);
    res.seven = r.seven + (r.sum ? l.sodd : l.seven);

    return res;
}

void build(int id, int l, int r) {
    if (l == r) {
        ST[id].ans = (a[l] ^ 1);
        ST[id].sum = a[l];

        ST[id].podd = ST[id].sodd = a[l];
        ST[id].peven = ST[id].seven = (a[l] ^ 1);
        return;
    }

    int m = (l + r) >> 1;
    build(id * 2, l, m);
    build(id * 2 + 1, m + 1, r);
    ST[id] = merge(ST[id * 2], ST[id * 2 + 1]);
}

void update(int id, int l, int r, int pos, int val) {
    if (l == r) {
        ST[id].ans = (a[l] ^ 1);
        ST[id].sum = a[l];

        ST[id].podd = ST[id].sodd = a[l];
        ST[id].peven = ST[id].seven = (a[l] ^ 1);
        return;
    }

    int m = (l + r) >> 1;
    if (pos <= m) update(id * 2, l, m, pos, val);
    else update(id * 2 + 1, m + 1, r, pos, val);

    ST[id] = merge(ST[id * 2], ST[id * 2 + 1]);
}

void query(int id, int l, int r, int tl, int tr, Node& res) {
    if (l > tr || r < tl) return;
    if (l >= tl && r <= tr) {
        if (l == tl) res = ST[id];
        else res = merge(res, ST[id]);
        return;
    }

    int m = (l + r) >> 1;
    query(id * 2, l, m, tl, tr, res);
    query(id * 2 + 1, m + 1, r, tl, tr, res);
}

signed main() {

    ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);

    // freopen("cf.inp", "r", stdin);
    // freopen("cf.out", "w", stdout);

    cin >> n >> q;
    for1(i,1,n) {
        cin >> a[i];
        a[i] &= 1;
    }

    build(1, 1, n);
    while (q--) {
        int op; cin >> op;
        if (op == 1) {
            int x, y;
            cin >> x >> y;
            a[x] = (y & 1);
            update(1, 1, n, x, y);
        }
        else {
            int l, r;
            cin >> l >> r;
            Node res;
            query(1, 1, n, l, r, res);
            cout << res.ans << "\n";
        }
    }

}

Bình luận

Hãy đọc nội quy trước khi bình luận.



  • 5
    NguyenHuuNhatQuang  đã bình luận lúc 28, Tháng 10, 2022, 16:44 chỉnh sửa

    Một cách khác:

    • Đầu tiên ta xây dựng mảng pref, với ~pref[i]~ lưu tính chẵn lẻ của prefix sum từ ~A[1]~ đến ~A[i]~. Mảng pref phải có kiểu dữ liệu bool để tránh bị TLE.
    • Với mỗi truy vấn loại 1, nếu số ~b~ nhập vào có số dư khi chia cho 2 khác với ~A[i]~, ta thực hiện phép lật bit (not) với tất cả các ~pref[i]~ với ~k \le i \le n~. Gán lại ~A[i]=b~.
    • Với mỗi truy vấn loại 2, ta sẽ gọi ~cnt1~ và ~cnt2~ là số lượng vị trí ~i~ sao cho ~l-1 \le i \le r~ sao cho ~pref[i]=1~ và ~pref[i]=0~. Dễ thấy ta chỉ cần duyệt mảng để tính ~cnt1~, ~cnt2~ sẽ được tính bằng công thức ~cnt2=r-l+2-cnt1~.
    • Nếu lấy hai vị trí ~m < n~ và ~pref[m] = pref[n]~ thì đoạn con từ ~m+1~ đến ~n~ có tổng là số chẵn. Do đó ta cần đếm số cặp ~(i, j)~ sao cho ~pref[i]=pref[j]=1~ hoặc ~pref[i]=pref[j]=0~, và kết quả sẽ là ~\frac{cnt1*(cnt1-1)+cnt2*(cnt2-1)}{2}~.

    Độ phức tạp: ~O(n^2)~, và lý do để cách code này không bị TLE, đó là do thao tác trên bit (TRUE/FALSE) nhanh gấp ~64~ lần thao tác trên long long, do đó ta có thể giảm thời gian chạy đi ~64~ lần, và ~\frac{10^5*10^5}{64}\approx 1,5*10^8~.


    • 0
      khanhphucscratch  đã bình luận lúc 29, Tháng 10, 2022, 12:17

      Nói chung em thấy thuật chuẩn vẫn ok hơn, vì nếu test mạnh mà code trâu thì mình toang anh ạ


    • 0
      khanhphucscratch  đã bình luận lúc 29, Tháng 10, 2022, 11:07

      Cho mình hỏi là bạn làm kiểu nào để 1,5 * 10^8 mà vẫn AC được vậy?


      • 1
        NguyenHuuNhatQuang  đã bình luận lúc 29, Tháng 10, 2022, 11:15

        à còn nếu ý em là vì sao ĐPT là 1,5 * 1e8 vẫn không TLE thì do máy chấm mạnh đó em, kẹp thêm mấy dòng lệnh căn bản

        ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        cout.tie(NULL);
        

        là nó đủ AC đấy em


      • 2
        NguyenHuuNhatQuang  đã bình luận lúc 29, Tháng 10, 2022, 11:09

        1,5*1e8 thì nhập xuất nó đã gây TLE rồi em ạ :v chưa cần truy vấn


        • 0
          khanhphucscratch  đã bình luận lúc 29, Tháng 10, 2022, 11:15

          Anh viết là sau khi giảm thời gian chạy 64 lần thì còn lại xấp xỉ 1,5 * 10^8 mà


          • 2
            NguyenHuuNhatQuang  đã bình luận lúc 29, Tháng 10, 2022, 11:19

            :v em có thể lấy code anh và thử hack :v


          • 1
            NguyenHuuNhatQuang  đã bình luận lúc 29, Tháng 10, 2022, 11:17

            hoặc có thể bài này test yếu thật, nhưng nhìn chung thì kha khá bài trâu theo kiểu 1e5^2 mà chủ yếu là tính bit thì vẫn không lố thời gian em ạ


          • 1
            NguyenHuuNhatQuang  đã bình luận lúc 29, Tháng 10, 2022, 11:16

            Rep ở trên rồi đó em


  • 14
    Giangcoder  đã bình luận lúc 20, Tháng 10, 2022, 12:16 chỉnh sửa

    *Góp ý:

    • Có một cách khác cài đơn giản hơn là ta chuyển từ mảng a[] qua mảng prefix sum (f[i]). sau đó ta lưu từng f[i] % 2 vào segment tree(lưu số lượng số 0 trong đoạn [l, r]). tới lúc update thì nếu x % 2 == a[i] % 2 thì không cần update, ngược lại ta cập nhập st[id] = (r - l + 1) - st[id] (chuyển toàn bộ số chẵn sang lẻ hoặc ngược lại). lúc get thì kết quả là số lượng cặp số (l, r) chẵn trong đoạn [u - 1, v] (l < r) cộng với số cặp lẻ(tương tự như cặp chẵn).