Skip to content
CP4CPP logo
CP4CPP/DPSOS
App settings
Installation and offline settings

Preparing update checks...

Workspaces

12

Reading record

Reading statistics
Reading progress
-
Words
-
Reading estimate
-
Preset chosen
-
Code blocks
-
Images and diagrams
-
Preferences and widgets

    Shared across workspaces

    Heading markersPercentage markers

    DP SOS: Quy hoạch động tổng tập con

    Experiments (1)

    DP SOS (Sum over Subsets) tính tổng giá trị của mọi tập con cho từng tập hợp. Bài học bắt đầu từ cách duyệt toàn bộ cặp mask, submask, sau đó giảm công việc lặp lại bằng quy hoạch động. Bạn sẽ thấy cùng một đáp án qua ba mức thời gian: O(4k)O(4^k), O(3k)O(3^k) và O(k2k)O(k2^k).

    Bài viết được biên soạn lại từ DP SOS của SPyofgame trên es.ac.vn, với biên tập viên tuandebu và người đóng góp ngpin04. Phần định nghĩa, hai cách cày trâu và các ví dụ đến từ bản gốc; phần duyệt tập con và truy hồi SOS là phần bổ sung của bản này. Xem nguồn và ghi chú biên soạn.

    • Mảng, vòng lặp, hàm đệ quy và phép cộng số nguyên trong C++
    • Biểu diễn nhị phân: bit thứ pp có trọng số 2p2^p, đánh số từ 00
    • Phép AND &, OR |, XOR ^ và dịch trái <<
    • Ý tưởng quy hoạch động: lưu và tái sử dụng kết quả của các bài toán con

    Nếu cần ôn C++, bắt đầu từ bài học nhập và xuất. Bài này thuộc nhóm quy hoạch động của CP Wiki.

    Xét tập nền U={0,1,…,k−1}U=\{0,1,\ldots,k-1\}. Một số nguyên mask trong [0,2k)[0,2^k) biểu diễn một tập con của UU: bit pp bằng 11 khi tập đó chứa pp. Ví dụ, 26=(11010)226=(11010)_2 biểu diễn tập {1,3,4}\{1,3,4\}.

    Cho mảng aa có 2k2^k phần tử. Với mỗi mask, cần tính

    F[mask]=∑submask⊆maska[submask].F[mask]=\sum_{submask\subseteq mask}a[submask].

    Ký hiệu submask⊆masksubmask\subseteq mask so sánh hai tập hợp được biểu diễn bằng bit, tương đương với (submask & mask) == submask. Nó không có nghĩa là submask <= mask: chẳng hạn 3≤43\leq4, nhưng (011)2(011)_2 không phải tập con của (100)2(100)_2.

    Tập rỗng có mã 00, nên a[0]a[0] được tính trong mọi tổng. Nếu bài toán không tính tập rỗng, có thể đặt a[0]=0a[0]=0 trước khi chạy. Nếu mảng ban đầu chỉ có n<2kn<2^k phần tử, chủ động thêm số 00 vào các vị trí n,…,2k−1n,\ldots,2^k-1.

    Với k=2k=2 và a=[1,2,3,4]a=[1,2,3,4]:

    maskBiểu diễn tập hợpCác submask hợp lệTổng
    00∅\varnothing0011
    01{0}\{0\}00, 011+2=31+2=3
    10{1}\{1\}00, 101+3=41+3=4
    11{0,1}\{0,1\}00, 01, 10, 111+2+3+4=101+2+3+4=10

    Kết quả là F=[1,3,4,10]F=[1,3,4,10]. Giá trị a[mask] là trọng số gắn với cả tập hợp, không phải tổng trọng số của từng bit trong tập hợp đó.

    Với mask = 26, chỉ các bit 1,3,41,3,4 được bật. Tám tập con có mã 0,2,8,10,16,18,24,260,2,8,10,16,18,24,26. Các giá trị trong hình gốc là:

    submask0281016182426
    a[submask]a[submask]89910124614

    Do đó F[26]=8+9+9+10+12+4+6+14=72F[26]=8+9+9+10+12+4+6+14=72.

    Tổng các tập con của 26
    flowchart TD
        total["F[26] = 72"] --> low["Không có bit 4: 36"]
        total --> high["Có bit 4: 36"]
        low --> l0["a[0] + a[2] = 17"]
        low --> l1["a[8] + a[10] = 19"]
        high --> h0["a[16] + a[18] = 16"]
        high --> h1["a[24] + a[26] = 20"]

    Ready when requested

    Sơ đồ chia tám tập con thành hai nhóm theo bit 44, rồi chia tiếp theo bit 33. Bảng và phép cộng phía trên vẫn cung cấp toàn bộ ví dụ khi chưa mở sơ đồ.

    Khởi tạo F[mask]=0F[mask]=0. Duyệt mọi mask và mọi submask trong [0,2k)[0,2^k); nếu submask là tập con của mask, cộng a[submask]a[submask] vào F[mask]F[mask].

    Hàm nhận mảng đã được thêm số 00 cho đủ 2k2^k phần tử. Các ví dụ C++ trong bài dùng long long; mọi tổng trung gian phải nằm trong miền biểu diễn của kiểu này.

    #include <cstddef>
    #include <vector>
    std::vector<long long> brute_sos(const std::vector<long long>& a) {
    std::vector<long long> result(a.size(), 0);
    for (std::size_t mask = 0; mask < a.size(); ++mask) {
    for (std::size_t submask = 0; submask < a.size(); ++submask) {
    if ((mask & submask) == submask) {
    result[mask] += a[submask];
    }
    }
    }
    return result;
    }

    Có 2k⋅2k=4k2^k\cdot2^k=4^k cặp cần kiểm tra. Phép AND kiểm tra tập con trong thời gian hằng số khi mask nằm trong một từ máy. Nếu thay phép AND bằng vòng lặp kiểm tra từng bit, thời gian có thể tăng thành O(k4k)O(k4^k).

    Với mỗi vị trí bit, xét bốn cặp giá trị (bit của mask, bit của submask): (0,0), (0,1), (1,0), (1,1). Khi đã chọn xong kk bit, kiểm tra quan hệ tập con rồi cộng vào kết quả.

    #include <cstddef>
    #include <vector>
    void brute_recursive(int bit, std::size_t mask, std::size_t submask,
    const std::vector<long long>& a,
    std::vector<long long>& result) {
    if (bit < 0) {
    if ((mask & submask) == submask) {
    result[mask] += a[submask];
    }
    return;
    }
    const std::size_t flag = std::size_t{1} << bit;
    brute_recursive(bit - 1, mask, submask, a, result);
    brute_recursive(bit - 1, mask, submask | flag, a, result);
    brute_recursive(bit - 1, mask | flag, submask, a, result);
    brute_recursive(bit - 1, mask | flag, submask | flag, a, result);
    }

    Trước khi gọi, tạo result gồm 2k2^k số 00, rồi gọi brute_recursive(k - 1, 0, 0, a, result). Điều kiện bit < 0 phải được xét trước phép dịch bit. Với k=0k=0, lời gọi đi thẳng vào trường hợp cơ sở và cho F[0]=a[0]F[0]=a[0].

    Cây đệ quy có 4k4^k lá. Tổng số nút là 1+4+⋯+4k1+4+\cdots+4^k, nên thời gian là O(4k)O(4^k); ngăn xếp có độ sâu O(k)O(k), ngoài mảng kết quả O(2k)O(2^k).

    Phần bổ sung này loại các cặp không hợp lệ ngay từ khi duyệt. Bắt đầu ở submask = mask, rồi lặp submask = (submask - 1) & mask để sinh tập con kế tiếp theo thứ tự giảm dần.

    #include <cstddef>
    #include <vector>
    std::vector<long long> submask_sos(const std::vector<long long>& a) {
    std::vector<long long> result(a.size(), 0);
    for (std::size_t mask = 0; mask < a.size(); ++mask) {
    std::size_t submask = mask;
    while (true) {
    result[mask] += a[submask];
    if (submask == 0) break;
    submask = (submask - 1) & mask;
    }
    }
    return result;
    }

    Phép trừ 11 tắt bit 11 thấp nhất và bật các bit thấp hơn; phép AND loại những bit không thuộc mask. Ví dụ, với mask = 10 (1010), thứ tự là 1010, 1000, 0010, 0000. Dừng sau khi xử lý 00 để vừa tính tập rỗng vừa tránh quay lại mask.

    Trong một cặp hợp lệ, mỗi bit có ba khả năng: không thuộc cả hai tập, chỉ thuộc mask, hoặc thuộc cả hai. Vì thế tổng số cặp trên mọi mask là 3k3^k. Có thể đạt cùng giới hạn bằng cách bỏ nhánh (0,1) khỏi hàm đệ quy.

    Phần bổ sung này giải thích cách tái sử dụng các tổng. Gọi Di[mask]D_i[mask] là tổng của những a[submask]a[submask] thỏa:

    • submask là tập con của mask
    • Các bit từ ii trở lên của submask giống mask
    • Chỉ các bit 0,…,i−10,\ldots,i-1 được phép thay đổi từ 11 thành 00

    Khi chưa xử lý bit nào, chỉ có chính mask, nên D0[mask]=a[mask]D_0[mask]=a[mask]. Khi đã xử lý cả kk bit, mọi tập con đều được tính, nên Dk[mask]=F[mask]D_k[mask]=F[mask].

    Công thức truy hồi và tính đúng đắn

    Section titled “Công thức truy hồi và tính đúng đắn”

    Nếu bit ii của mask bằng 00, mọi tập con cũng có bit này bằng 00, nên không có nhóm mới. Nếu bit đó bằng 11, chia các tập con thành hai nhóm rời nhau: giữ bit ii và bỏ bit ii.

    Di+1[mask]={Di[mask],(mask&2i)=0,Di[mask]+Di[mask⊕2i],(mask&2i)≠0.D_{i+1}[mask]= \begin{cases} D_i[mask], & (mask\mathbin{\&}2^i)=0,\\ D_i[mask]+D_i[mask\mathbin{\oplus}2^i], & (mask\mathbin{\&}2^i)\ne0. \end{cases}

    Hai nhóm bao phủ toàn bộ các tập con được phép sau bước ii và không giao nhau. Từ trường hợp cơ sở, quy nạp theo ii cho thấy mỗi giá trị được cộng đúng một lần vào tổng cần thiết.

    Có thể ghi đè trên một mảng: tại bước ii, chỉ các mask có bit i=1i=1 bị cập nhật. Mọi ô được đọc thêm đều có bit i=0i=0, nên chưa bị thay đổi trong bước đó. Vì vậy thứ tự duyệt mask trong cùng một bước bit không ảnh hưởng kết quả.

    Vẫn dùng a=[1,2,3,4]a=[1,2,3,4]:

    Bước00011011
    Khởi tạo D0=aD_0=a1234
    Xử lý bit 012+1=32+1=334+3=74+3=7
    Xử lý bit 1133+1=43+1=47+3=107+3=10

    Phải đặt vòng lặp bit ở ngoài khi ghi đè theo cách này. Nếu đổi sang mask ở ngoài, với ví dụ trên có thể thu được 1111 thay vì 1010 ở mask 11, vì a[0]a[0] bị cộng lặp.

    Chương trình đọc kk, tiếp theo là đúng 2k2^k số nguyên. Giới hạn 0≤k≤200\leq k\leq20 và ∣a[i]∣≤109|a[i]|\leq10^9 là lựa chọn cho ví dụ này: tổng trị tuyệt đối không vượt 220⋅1092^{20}\cdot10^9, nằm trong long long. Đây không phải giới hạn toán học của SOS DP.

    #include <cstddef>
    #include <iostream>
    #include <string>
    #include <vector>
    std::vector<long long> sos_dp(std::vector<long long> values, int k) {
    for (int bit = 0; bit < k; ++bit) {
    const std::size_t flag = std::size_t{1} << bit;
    for (std::size_t mask = 0; mask < values.size(); ++mask) {
    if ((mask & flag) != 0) {
    values[mask] += values[mask ^ flag];
    }
    }
    }
    return values;
    }
    int main() {
    int k;
    if (!(std::cin >> k) || k < 0 || k > 20) {
    std::cerr << "Expected integer k in [0, 20]\n";
    return 1;
    }
    const std::size_t count = std::size_t{1} << k;
    std::vector<long long> a(count);
    for (long long& value : a) {
    if (!(std::cin >> value) || value < -1000000000LL || value > 1000000000LL) {
    std::cerr << "Expected 2^k integers in [-10^9, 10^9]\n";
    return 1;
    }
    }
    std::string extra;
    if (std::cin >> extra) {
    std::cerr << "Unexpected extra input\n";
    return 1;
    }
    for (long long value : sos_dp(a, k)) {
    std::cout << value << '\n';
    }
    }

    Các hàm ở trên giả sử độ dài mảng bằng 2k2^k. Hàm main bảo đảm điều kiện này; khi dùng riêng hàm sos_dp, người gọi phải bảo đảm kk hợp lệ, đúng kích thước mảng và không tràn tổng.

    Ví dụ đầu tiên của bản gốc:

    2
    27 2 2004 0

    Kết quả, theo thứ tự mask từ 00 đến 33:

    27
    29
    2031
    2033

    Các ví dụ còn lại giúp phân biệt quy ước mảng và dữ liệu nhập không hợp lệ. Chương trình mới báo lỗi với dữ liệu thiếu hoặc thừa; muốn thêm số 00 thì phải làm rõ điều đó trước khi tính.

    Tình huống từ bản gốcCách dùng trong bản này
    k=2k=2, dãy 1 2 3 4 5Báo thừa dữ liệu; dãy hợp lệ 1 2 3 4 cho 1 3 4 10
    k=3k=3, chỉ có 27 2 2004Báo thiếu dữ liệu; thêm năm số 00 thì được 27 29 2031 2033 27 29 2031 2033
    k=0k=0, không có phần tử nàoVẫn cần một phần tử a[0]a[0]; nhập 0 rồi 0 thì kết quả là 0
    k=−1k=-1Báo lỗi trước khi dịch bit hoặc cấp phát
    k=31k=31Vượt giới hạn 2020 của chương trình, báo lỗi trước khi cấp phát

    Không thể suy ra rằng mọi trình biên dịch sẽ phát sinh cùng một ngoại lệ cho phép dịch với kk âm hoặc quá lớn. Tránh thực hiện phép dịch không hợp lệ ngay từ đầu.

    Độ phức tạp và các lỗi dễ gặp

    Section titled “Độ phức tạp và các lỗi dễ gặp”
    Cách tínhThời gianBộ nhớ ngoài đầu vào
    Hai vòng lặp, kiểm tra bằng ANDO(4k)O(4^k)O(2k)O(2^k)
    Đệ quy bốn nhánhO(4k)O(4^k)O(2k+k)O(2^k+k)
    Duyệt trực tiếp từng submaskO(3k)O(3^k)O(2k)O(2^k)
    SOS DP theo bitO(k2k)O(k2^k)O(2k)O(2^k) với bản sao kết quả

    Với k≥1k\geq1, vòng DP kiểm tra k2kk2^k mask và thực hiện đúng k2k−1k2^{k-1} phép cộng. Với k=0k=0, chỉ có một phần tử và không có bước bit nào. Các giới hạn trên dùng mô hình phép toán số nguyên có kích thước cố định; số nguyên lớn cần tính thêm chi phí từng phép cộng.

    • Không quên tập rỗng: F[0]=a[0]F[0]=a[0]
    • Cày trâu bắt đầu từ mảng kết quả toàn 00; SOS DP bắt đầu từ bản sao của aa
    • Nếu ∣a[i]∣≤M|a[i]|\leq M, thì ∣F[mask]∣≤2popcount⁡(mask)M≤2kM|F[mask]|\leq2^{\operatorname{popcount}(mask)}M\leq2^kM, không phải kMkM
    • Cấp phát 2k2^k phần tử tăng rất nhanh: riêng 2302^{30} số nguyên 64 bit cần 88 GiB, chưa tính mảng khác
    • Chỉ kiểm tra giới hạn phép dịch là chưa đủ; cần xét cả bộ nhớ và thời gian
    • Nếu tính theo modulo, áp dụng modulo khi cộng và chuẩn hóa số âm theo yêu cầu bài toán
    1. Với a=[5,−2,7,1]a=[5,-2,7,1], hãy tính FF bằng tay
    2. Với mask = 5 (101), liệt kê mọi tập con theo thứ tự giảm dần
    3. Nếu mọi a[mask]=1a[mask]=1, F[mask]F[mask] phụ thuộc vào số bit bật như thế nào?
    Đáp án
    1. F=[5,3,12,11]F=[5,3,12,11]
    2. 101, 100, 001, 000, tức 5,4,1,05,4,1,0
    3. F[mask]=2popcount⁡(mask)F[mask]=2^{\operatorname{popcount}(mask)}, vì mỗi bit bật có hai lựa chọn: giữ hoặc bỏ
    • Bản gốc: es.ac.vn/docs/wiki/dp/dp_sos/dp_sos.md và các tệp được include; frontmatter ghi giấy phép Public domain
    • Tác giả bản gốc: SPyofgame; biên tập viên: tuandebu; người đóng góp: ngpin04
    • Tham khảo đối chiếu phần bổ sung: USACO Guide: Sum over Subsets DP, gồm duyệt submask và cập nhật tổng theo bit

    Bản này giữ các ví dụ số của nguồn, viết lại sơ đồ thành cây chia nhóm nhỏ, sửa đối số bị thiếu trong lời gọi đệ quy và phân biệt lỗi nhập liệu với việc chủ động thêm số 00. Các khối MkDocs, trường NULL và lời khẳng định kiểm thử không kèm bằng chứng của bản gốc được thay bằng nội dung đọc được trong trình đọc hiện tại. Đây là bản biên soạn hỗ trợ bằng AI; chưa ghi nhận phê duyệt chuyên môn độc lập cho bản biên soạn này.

    Reader layout

    Col / Left

    Left column

    LEFT
    Width
    Rail
    ...
    CENTER
    Width
    Rail
    ...
    RIGHT
    Width
    Rail
    ...
    TOP
    NAV
    MID
    BOT

    Theme settings

    Preset 0 · Default · Locked

    Advanced colors · OKLCH

    Choose preset 1-9 to edit. Changes save on this device. Each color inherits in this order: cell → row → column → app → base theme.

    Editing All

    Selected scope

    Chroma 0 is neutral gray. Displayable colors depend on your screen; the browser maps colors outside its gamut. Grayscale also affects images and charts.

    Progress appearance

    Loading controls…

    Forest0.88 kB
    Slate0.88 kB

    Font settings

    Preset 0 · Default · Locked

    Font typography
    Advanced typography

    Auto uses the selected spacing mode. Variants and available weights depend on the font. The browser may synthesize missing weights or styles.

    a b c d e f g h i j k l m n o p q r s t u v w x y z

    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

    0 1 2 3 4 5 6 7 8 9

    ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    Iosevka Extended10.76 MB
    Inter297.90 kB
    Source Sans 3197.90 kB
    Lora138.11 kB
    Source Serif 4216.02 kB
    JetBrains Mono130.64 kB
    Fira Code53.67 kB

    Keybinds

    Choose a binding, press up to 4 keys together, then release to save. Modifiers count as keys. Esc clears the selected binding. Unsupported keys show Unknown.

    Reader

    Forbidden key combinations

    Windows / Linux reference. These combinations are reserved by this app to avoid browser, system and editing conflicts. Some are intercepted before a page can receive them. Browser extensions and other operating systems may reserve additional keys.

    Fixed navigation: Ctrl + K opens Search. Alt + F6-F9 selects a row; Alt + F10-F12 selects a column. Combine both to select a cell. While selected, F1 captures a screenshot and F2 downloads PNG. Esc cancels dialogs and navigation when a binding is not being recorded.