Educational Codeforces Round 171 div2(A~E)

ExtractStars發表於2024-10-29

Educational Codeforces Round 171 div2(A~E)

Dashboard - Educational Codeforces Round 171 (Rated for Div. 2) - Codeforces

火車頭

#define _CRT_SECURE_NO_WARNINGS 1

#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace std;

#define ft first
#define sd second

#define yes cout << "yes\n"
#define no cout << "no\n"

#define Yes cout << "Yes\n"
#define No cout << "No\n"

#define YES cout << "YES\n"
#define NO cout << "NO\n"

#define pb push_back
#define eb emplace_back

#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define sort_all(x) sort(all(x))
#define sort1_all(x) sort(all1(x))

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL

#define RED cout << "\033[91m"     // 紅色
#define GREEN cout << "\033[92m"   // 綠色
#define YELLOW cout << "\033[93m"  // 藍色
#define BLUE cout << "\033[94m"    // 品紅
#define MAGENTA cout << "\033[95m" // 青色
#define CYAN cout << "\033[96m"    // 青色
#define RESET cout << "\033[0m"    // 重置

template <typename T>
void Debug(T x, int color = 1)
{
    switch (color)
    {
    case 1:
        RED;
        break;
    case 2:
        YELLOW;
        break;
    case 3:
        BLUE;
        break;
    case 4:
        MAGENTA;
        break;
    case 5:
        CYAN;
        break;
    default:
        break;
    }
    cout << x;
    RESET;
}


typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;

typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pdd;
typedef pair<ll, int> pli;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;

typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;

typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pli> vpli;
typedef vector<pss> vpss;
typedef vector<ti3> vti3;
typedef vector<tl3> vtl3;
typedef vector<tld3> vtld3;

typedef vector<vi> vvi;
typedef vector<vl> vvl;

typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;

typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;

typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;

typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;

std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());

template <typename T>
inline T read()
{
    T x = 0;
    int y = 1;
    char ch = getchar();
    while (ch > '9' || ch < '0')
    {
        if (ch == '-')
            y = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * y;
}

template <typename T>
inline void write(T x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x >= 10)
    {
        write(x / 10);
    }
    putchar(x % 10 + '0');
}

/*#####################################BEGIN#####################################*/
void solve()
{

}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 連結:

A. Perpendicular Segments

給定一個座標平面和三個整數 \(X\)\(Y\)\(K\)。找出兩條線段 \(AB\)\(CD\),使得

  • \(A\)\(B\)\(C\)\(D\) 的座標為整數;
  • \(0 \leq A_x, B_x, C_x, D_x \leq X\)\(0 \leq A_y, B_y, C_y, D_y \leq Y\)
  • 線段 \(AB\) 的長度至少為 \(K\)
  • 線段 \(CD\) 的長度至少為 \(K\)
  • 線段 \(AB\)\(CD\) 垂直:如果繪製包含 \(AB\)\(CD\) 的線,它們將以直角相交。

請注意,線段不必須相交。只要它們匯出的線垂直,線段就是垂直的。

輸入
第一行包含一個整數 \(t\) ( \(1 \leq t \leq 5000\) ) — 測試用例的數量。接下來是 \(t\) 個用例。

每個測試用例的第一行也是唯一一行包含三個整數 \(X\)\(Y\)\(K\) ( \(1 \leq X, Y \leq 1000\) ; \(1 \leq K \leq 1414\) )。

對輸入的附加約束:選擇 \(X\)\(Y\)\(K\) 的值,使得答案存在。

輸出
對於每個測試用例,列印兩行。第一行應包含 \(4\) 個整數 \(A_x\)\(A_y\)\(B_x\)\(B_y\) ——第一段的座標。

第二行還應包含 \(4\) 個整數 \(C_x\)\(C_y\)\(D_x\)\(D_y\) ——第二段的座標。

如果有多個答案,請列印其中任何一個。

示例
輸入

4
1 1 1
3 4 1
4 3 3
3 4 4

輸出

0 0 1 0
0 0 0 1
2 4 2 2
0 1 1 1
0 0 1 3
1 2 4 1
0 1 3 4
0 3 3 0

提示
第一個測試用例的答案如下:

img

第二個測試用例的答案:

img

第三個測試用例的答案:

img

第四個測試用例的答案:

img

解題思路

對於矩形來說\(\max\{\min(|AB|,|CD|)\}=最大正方形對角線,AB \perp CD\)

具體證明我也不太清楚該怎麼證。

程式碼實現

void solve()
{
    ll X, Y, K;
    cin >> X >> Y >> K;
    ll mn = min(X, Y);
    cout << 0 << " " << 0 << " " << mn << " " << mn << "\n";
    cout << 0 << " " << mn << " " << mn << " " << 0 << endl;
}

B. Black Cells

您將得到一個分成多個單元格的條帶,從左到右編號為 \(0\)\(10^{18}\)。最初,所有單元格都是白色的。

您可以執行以下操作:選擇兩個白色單元格 \(i\)\(j\),使得 \(i \neq j\)\(|i - j| \leq k\),並將它們塗成黑色。

給出了一個列表 \(a\)。此列表中的所有單元格都必須塗成黑色。此外,最多一個不在此列表中的單元格也可以塗成黑色。您的任務是確定 \(k\) 的最小值,以實現此目的。

輸入
第一行包含一個整數 \(t\) (\(1 \leq t \leq 500\)) — 測試用例的數量。

每個測試用例的第一行包含一個整數 \(n\) (\(1 \leq n \leq 2000\))。

第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) (\(0 < a_i < 10^{18}\); \(a_i < a_{i+1}\))。

對輸入的附加約束:所有測試用例的 \(n\) 之和不超過 \(2000\)

輸出
對於每個測試用例,列印一個整數—— \(k\) 的最小值,可以將所有給定單元格塗成黑色。

示例
輸入

4
2
1 2
1
7
3
2 4 9
5
1 5 8 10 13

輸出

1
1
2
3

提示
在第一個示例中,\(k=1\) 時,可以塗黑單元格 \((1, 2)\)

在第二個示例中,\(k=1\) 時,可以塗黑單元格 \((7, 8)\)

在第三個示例中,\(k=2\) 時,可以塗黑單元格 \((2, 4)\)\((8, 9)\)

在第四個示例中,\(k=3\) 時,可以塗黑單元格 \((0, 1)\)\((5, 8)\)\((10, 13)\)

解題思路

  • 方法一

    對於\(n\)為偶數的情況,一定是連續兩個兩個進行分配最優,對於奇數情況,我們可以列舉和列表外一起塗的單元格,剩下的單元格兩個兩個進行分配,統計最小值。

    時間複雜度\(O(n^2)\)

  • 方法二

    題目要求最小化可行值,具有二分性,考慮二分\(k\)值。

    對於一個指定的\(k\)值,我們可以使用使用\(dp\)去檢驗其是否可行。

    \(dp[i][j]\)為對於前\(i\)個元素,使用\(j\)次列表外元素是否可行,易得狀態轉移方程

    \[dp[i][j]= \begin{cases} dp[i-2][j] & a[i]-a[i-1]\le k \\ dp[i-1][j-1] & a[i]-a[i-1]\gt k \end{cases} \]

​ 時間複雜度\(O(n\log{V})\)

程式碼實現

方法一

void solve()
{
    ll n;
    cin >> n;
    vl a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    ll ans;
    if (n % 2 == 0)
    {
        ans = 0;
        for (int i = 1; i < n; i += 2)
        {
            ans = max(ans, a[i] - a[i - 1]);
        }
    }
    else
    {
        ans = infll;
        for (int i = 0; i < n; i++)
        {
            int cnt = 0;
            int p = -1;
            ll minK = 1;
            for (int j = 0; j < n; j++)
            {
                if (j == i)
                    continue;
                cnt++;
                if (cnt == 1)
                {
                    p = j;
                }
                else if (cnt == 2)
                {
                    cnt = 0;
                    minK = max(minK, a[j] - a[p]);
                }
            }
            ans = min(ans, minK);
        }
    }
    cout << ans << '\n';
}

方法二

void solve()
{
    int n;
    cin >> n;
    vl a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    auto check = [&](ll k) -> bool
    {
        vector<vb> dp(n + 2, vb(2));
        dp[0][0] = true; // 可以塗黑0個單元格
        dp[1][1] = true; // 可以使用一次列表外單元格塗黑第一個單元格
        for (int i = 2; i <= n + 1; i++)
        {
            for (int j = 0; j < 2; ++j)
            {
                if (!dp[i - 2][j]) // 如果當前狀態不可達,跳過
                    continue;
                // 嘗試塗黑下一個單元格
                if (a[i] - a[i - 1] <= k)
                    dp[i][j] = true; // 塗黑 a[i] 和 a[i-1]
                // 如果還沒有使用額外的單元格,嘗試使用它
                if (j == 0)
                    dp[i - 1][1] = true; // 塗黑 a[i-1] 和額外的單元格
            }
        }
        return dp[n][0] || dp[n][1];
    };
    ll l = 1, r = 1e18;
    while (l < r)
    {
        ll mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << l << '\n';
}

C. Action Figures

Monocarp 家附近有一家商店出售可動人偶。一套新的可動人偶將很快釋出;這套包含 \(n\) 個人偶,第 \(i\) 個人偶花費 \(i\) 枚硬幣,可從第 \(i\) 天到第 \(n\) 天購買。

對於每 \(n\) 天,Monocarp 都知道他是否可以訪問商店。

每次 Monocarp 訪問商店時,他都可以購買商店中出售的任意數量的可動人偶(當然,他不能購買尚未可供購買的可動人偶)。如果 Monocarp 在同一天購買至少兩個人偶,他將獲得相當於他購買的最貴人偶價格的折扣(換句話說,他可以免費獲得他購買的人偶中最貴的那個)。

Monocarp 想要從該套裝中購買恰好一個第 \(1\) 個玩偶、一個第 \(2\) 個玩偶、...、一個第 \(n\) 個玩偶。他不能購買同一個玩偶兩次。他必須花費的最低金額是多少?

輸入
第一行包含一個整數 \(t\) (\(1 \leq t \leq 10^4\)) — 測試用例的數量。

每個測試用例由兩行組成:

第一行包含一個整數 \(n\) (\(1 \leq n \leq 4 \cdot 10^5\)) — 集合中的數字數量(和天數);
第二行包含一個字串 \(s\) (\(|s|=n\),每個 \(s_i\)\(0\)\(1\))。如果 Monocarp 可以在第 \(i\) 天訪問商店,則 \(s_i\)\(1\);否則,\(s_i\)\(0\)
對輸入的其他約束:

在每個測試用例中,\(s_n\)\(1\),因此 Monocarp 總是能夠在第 \(n\) 天購買所有數字;
所有測試用例的 \(n\) 之和不超過 \(4 \cdot 10^5\)

輸出
對於每個測試用例,列印一個整數 — Monocarp 必須花費的最低金額。

示例
輸入

4
1
1
6
101101
7
1110001
5
11111

輸出

1
8
18
6

提示
在第一個測試用例中,Monocarp 在第 \(1\) 天購買第 \(1\) 個人偶,花費 \(1\) 枚硬幣。

在第二個測試用例中,Monocarp 可以在第 \(3\) 天購買第 \(1\) 和第 \(3\) 個人偶,在第 \(4\) 天購買第 \(2\) 和第 \(4\) 個人偶,在第 \(6\) 天購買第 \(5\) 和第 \(6\) 個人偶。然後,他將花費 \(1 + 2 + 5 = 8\) 枚硬幣。

在第三個測試用例中,Monocarp 可以在第 \(3\) 天購買第 \(2\) 和第 \(3\) 個人偶,在第 \(7\) 天購買所有其他人偶。然後,他將花費 \(1 + 2 + 4 + 5 + 6 = 18\) 枚硬幣。

解題思路

我們可以從後往前遍歷每一天,記錄可以購買的人偶。對於每一天,如果 Monocarp 可以訪問商店並且有可購買的人偶,我們將其加入佇列。

若當天無法購買人偶,我們可以把這個人偶和佇列中的人偶進行匹配,將最貴的人偶零元購。

程式碼實現

void solve()
{
    int n;
    cin >> n;
    string s;
    cin >> s;
    s = '#' + s;
    ll sum = n * (1 + n) / 2;     // 計算所有人偶的總花費
    priority_queue<int> waitFree; // 大根堆儲存等待零元購的人偶

    for (int i = n; i >= 1; i--)
    {
        if (s[i] == '1')
        {
            waitFree.push(i); // 如果今天可以訪問商店,加入待免費佇列
        }
        else if (!waitFree.empty())
        {
            sum -= waitFree.top(); // 如果今天無法訪問商店,取出最貴的人偶與當前人偶i進行匹配
            waitFree.pop();
        }
    }

    if (!waitFree.empty())
    {
        vi remain;
        while (!waitFree.empty())
        {
            remain.eb(waitFree.top());
            waitFree.pop();
        }
        for (int i = 0; i <= (int)remain.size() / 2 - 1; i++)
        {
            sum -= remain[i]; // 把最貴的那一半人偶零元購
        }
    }
    cout << sum << endl;
}

D. Sums of Segments

給定一個整數序列 \([a_1, a_2, \ldots, a_n]\)。令 \(s(l, r)\) 為從 \(a_l\)\(a_r\)(即 \(s(l, r) = \sum_{i=l}^r a_i\))元素的總和。

讓我們構建另一個大小為 \(\frac{n(n+1)}{2}\) 的序列 \(b\),如下所示:
\(b = [s(1, 1), s(1, 2), \ldots, s(1, n), s(2, 2), s(2, 3), \ldots, s(2, n), s(3, 3), \ldots, s(n, n)]\)

例如,如果 \(a = [1, 2, 5, 10]\),則 \(b = [1, 3, 8, 18, 2, 7, 17, 5, 15, 10]\)

您有 \(q\) 個查詢。在第 \(i\) 個查詢期間,您有兩個整數 \(l_i\)\(r_i\),您必須計算 \(\sum_{j=l_i}^{r_i} r_j b_j\)

輸入
第一行包含一個整數 \(n\) (\(1 \leq n \leq 3 \cdot 10^5\))。

第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) (\(-10 \leq a_i \leq 10\))。

第三行包含一個整數 \(q\) (\(1 \leq q \leq 3 \cdot 10^5\))。

接下來是 \(q\) 行,其中第 \(i\) 行包含兩個整數 \(l_i\)\(r_i\) (\(1 \leq l_i \leq r_i \leq \frac{n(n+1)}{2}\))。

輸出
列印 \(q\) 個整數,其中第 \(i\) 個應等於 \(\sum_{j=l_i}^{r_i} r_j b_j\)

示例
輸入

4
1 2 5 10
15
1 1
1 2
1 3
1 4
1 5
1 10
5 10
6 10
2 8
3 4
3 10
3 8
5 6
5 5
1 8

輸出

1
4
12
30
32
86
56
54
60
26
82
57
9
2
61

解題思路

對於題目給出的序列\(b\),我們可以再構建\(n\)個分割槽將序列\(b\)進行劃分。

其中\(分割槽i=[\text{s}(i,i),\text{s}(i,i+1),\text{s}(i,i+2),\dots,\text{s}(i,n)]\)

每個分割槽長度\(\text{len}_i=n-i+1\)

對於第\(i\)個分割槽,它的分割槽和\(\text{val}_i=\sum_{j=i}^{n}\text{pre}_j-\text{len}_i\times\text{pre}_{i-1}\),其中\(\text{pre}_j=\sum_{i=1}^ja_i\)

對於每個查詢\([l,r]\),我們實際可以先求出\(\text{preb}_{l-1}= \sum_{i=1}^{l-1}b_i\)\(\text{preb}_{r}= \sum_{i=1}^{r}b_i\),答案則為\(\text{preb}_r-\text{preb}_{l-1}\)

\(\text{preb}_p=\sum_{i=1}^{j}\text{val}_i+\sum_{k=j+1}^{ p-\text{pos}_j+j}-(p-\text{}pos_j)\times pre_j\),其中\(j\)為在\(p\)之前的最大完整分割槽編號,\(pos_j\)為分割槽\(j\)的結束位置

對於\(j\)我們可以二分求出,\(pos\)則可以根據\(len\)求。

時間複雜度為\(O(n+q\log n)\)

程式碼實現

void solve()
{
    ll n;
    cin >> n;
    vl a(n + 1);
    vl pre(n + 1);     // 字首和
    vl pre_pre(n + 1); // 字首和的字首和
    vl pos(n + 1);     // 儲存分割槽i的結束位置
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
        pre_pre[i] = pre_pre[i - 1] + pre[i];
        pos[i] = n - i + 1; // 第i個分割槽長度為 n - i + 1
        pos[i] += pos[i - 1];
    }
    vl pre_val(n + 1); // 儲存每個分割槽值的字首和

    for (ll i = 1; i <= n; i++)
    {
        pre_val[i] = pre_pre[n] - pre_pre[i - 1] - (n - i + 1) * (pre[i - 1]);
        pre_val[i] += pre_val[i - 1];
    }
    auto query = [&](ll p) -> ll
    {
        if (p == 0)
            return 0;
        // 二分查詢,找到最大的 l 使得 pos[l] <= p,查詢到最大完整分割槽編號
        ll l = 0, r = n + 1;
        while (r - l > 1)
        {
            ll mid = l + r >> 1;
            if (pos[mid] <= p)
                l = mid;
            else
                r = mid;
        }
        ll ans = pre_val[l]; // 將字首分割槽和加入答案
        // 列舉不完整分割槽
        p -= pos[l] - l;
        // 計算不完整分割槽區間和
        ans += pre_pre[p] - pre_pre[l] - (p - l) * (pre[l]);
        return ans;
    };

    ll q;
    cin >> q;
    while (q-- > 0)
    {
        ll l, r;
        cin >> l >> r;
        cout << query(r) - query(l - 1) << '\n';
    }
}

E. Best Subsequence

給定一個大小為 \(n\) 的整數陣列 \(a\)

我們將陣列的值定義為其大小減去陣列所有元素按位或中的設定位數。

例如,對於陣列 \([1, 0, 1, 2]\),按位或為 \(3\)(包含 \(2\) 個設定位),陣列的值為 \(4 - 2 = 2\)

您的任務是計算給定陣列的某個子序列的最大可能值。

輸入
第一行包含一個整數 \(t\) (\(1 \leq t \leq 100\)) — 測試用例的數量。

每個測試用例的第一行包含一個整數 \(n\) (\(1 \leq n \leq 100\))。

每個測試用例的第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) (\(0 \leq a_i < 2^{60}\))。

輸出
對於每個測試用例,列印給定陣列的某個子序列的最大可能值。

示例
輸入

4
3
0 0 0
4
1 0 1 2
1
5
8
7 1 48 14 13 8 7 6

輸出

3
2
0
3

解題思路

最大權閉合子圖問題

相關部落格推薦:

  • 最小割 - OI Wiki
  • 最大權閉合子圖 - 洛谷專欄
  • 最大權閉合子圖_閉合圖-CSDN部落格
  • 最大權閉合子圖 - 知乎

題目大意

給定一個整數陣列,選擇一個子序列,使其 "值" 最大化。這裡 "值" 定義為子序列的長度減去其所有元素按位或後結果中設定位的數量。

建模思路

  • 每選擇一個元素,可以增加子序列的長度,但同時可能增加按位或結果中設定位的數量。
  • 目標是最大化 \(子序列長度 - 按位或結果的設定位數\)
  • 反向思考,可以最小化選擇的元素數以覆蓋所有可能設定位,進而最大化 \(n - 元素數\)

建圖

從源點 \(s\) 到每個元素節點 \(i\) 新增容量為 \(1\) 的邊,表示每個元素可以被選中一次。

對於每個元素 \(i\),遍歷其二進位制表示的每一位 \(j\)

  • 如果第 \(j\) 位為 \(1\),則從元素節點 \(i\) 到位元位節點 \(n + j\) 新增一條容量為無窮大的邊,表示選擇元素 \(i\) 可以覆蓋第 \(j\) 位。

  • 從每個位元位節點 \(n + j\) 到匯點 \(t\) 新增容量為 \(1\) 的邊,表示每個位只能被一個元素覆蓋。

最大流代表覆蓋的位元位的最大數量,因此選中的元素數最少, \(n - 最大流\)即為答案。

程式碼實現

// Dinic演算法求最大流
template <class T>
struct MaxFlow
{
    // 內部結構體,表示圖中的一條邊
    struct _Edge
    {
        int to;                                    // 邊的終點
        T cap;                                     // 邊的容量
        _Edge(int to, T cap) : to(to), cap(cap) {} // 邊的建構函式
    };

    int n;                 // 圖中節點的數量
    vector<_Edge> e;       // 儲存所有邊
    vector<vector<int>> g; // 鄰接表,g[u]儲存與節點u相連的所有邊的索引
    vector<int> cur, h;    // 當前正在探索的邊的指標和節點的高度(層次)

    // 預設建構函式
    MaxFlow() {}

    // 帶節點數量的建構函式,初始化圖
    MaxFlow(int n)
    {
        init(n);
    }

    // 初始化圖的節點數量,並清空之前的邊和鄰接表
    void init(int n)
    {
        this->n = n;
        e.clear();        // 清空所有邊
        g.assign(n, {});  // 初始化鄰接表,給每個節點分配一個空的邊列表
        cur.assign(n, 0); // 初始化當前邊指標陣列為0
        h.assign(n, 0);   // 初始化高度陣列為0
    }

    // 廣度優先搜尋,用於構建層次圖
    bool bfs(int s, int t)
    {
        h.assign(n, -1); // 將所有節點的高度初始化為-1,表示未訪問
        queue<int> que;  // 建立一個佇列用於BFS
        h[s] = 0;        // 源點的高度設為0
        que.push(s);     // 將源點入隊

        while (!que.empty())
        {
            int u = que.front(); // 取出隊頭節點
            que.pop();
            for (int i : g[u]) // 遍歷節點u的所有邊
            {
                auto [v, c] = e[i];      // 獲取邊的終點v和剩餘容量c
                if (c > 0 && h[v] == -1) // 如果邊有剩餘容量且v未被訪問
                {
                    h[v] = h[u] + 1; // 更新v的高度,即層次
                    if (v == t)
                        return true; // 如果到達匯點,層次圖已經構建完成
                    que.push(v);     // 將v入隊,繼續BFS
                }
            }
        }
        return false; // 如果無法到達匯點,返回false
    }

    // 深度優先搜尋,用於在層次圖中尋找增廣路徑並進行流量增廣
    T dfs(int u, int t, T f)
    {
        if (u == t)
            return f;                                    // 到達匯點,返回當前增廣的流量f
        T r = f;                                         // 剩餘需要增廣的流量
        for (int &i = cur[u]; i < int(g[u].size()); ++i) // 遍歷節點u的所有邊,從當前邊指標開始
        {
            int j = g[u][i];               // 獲取邊的索引
            auto [v, c] = e[j];            // 獲取邊的終點v和剩餘容量c
            if (c > 0 && h[v] == h[u] + 1) // 如果邊有剩餘容量且v在層次圖中的高度是u的高度加1
            {
                T a = dfs(v, t, min(r, c)); // 遞迴地嘗試增廣流量,選擇最小的流量
                if (a > 0)
                {
                    e[j].cap -= a;     // 減少正向邊的剩餘容量
                    e[j ^ 1].cap += a; // 增加反向邊的剩餘容量
                    r -= a;            // 減少需要增廣的剩餘流量
                    if (r == 0)
                        return f; // 如果已經沒有剩餘流量需要增廣,返回
                }
            }
        }
        return f - r; // 返回實際增廣的流量
    }

    // 向圖中新增一條從u到v的邊,並新增對應的反向邊
    void addEdge(int u, int v, T c)
    {
        g[u].push_back(e.size()); // 將邊的索引新增到u的鄰接表中
        e.emplace_back(v, c);     // 新增正向邊
        g[v].push_back(e.size()); // 將反向邊的索引新增到v的鄰接表中
        e.emplace_back(u, 0);     // 新增反向邊,初始容量為0
    }

    // 計算從源點s到匯點t的最大流
    T flow(int s, int t)
    {
        T ans = 0;        // 初始化最大流量為0
        while (bfs(s, t)) // 當存在從s到t的增廣路徑時
        {
            // 在每次BFS後需要重置當前邊指標
            // 這是為了防止在DFS過程中重複遍歷已經嘗試過的邊
            cur.assign(n, 0);
            // 嘗試透過DFS增廣流量,新增到總流量中
            ans += dfs(s, t, numeric_limits<T>::max());
        }
        return ans; // 返回最終的最大流量
    }

    // 計算最小割,返回一個布林陣列,表示每個節點是否在割集的一側
    vector<bool> minCut()
    {
        vector<bool> c(n);
        for (int i = 0; i < n; i++)
            c[i] = (h[i] != -1); // 如果節點i的高度不為-1,表示可達源點側
        return c;                // 返回最小割結果
    }

    // 定義一個邊結構體,用於儲存邊的詳細資訊
    struct Edge
    {
        int from; // 邊的起點
        int to;   // 邊的終點
        T cap;    // 邊的容量
        T flow;   // 邊的流量
    };

    // 獲取所有的邊資訊
    vector<Edge> edges()
    {
        vector<Edge> a;
        for (int i = 0; i < e.size(); i += 2) // 遍歷所有正向邊
        {
            Edge x;
            x.from = e[i + 1].to;            // 反向邊的終點作為起點
            x.to = e[i].to;                  // 正向邊的終點
            x.cap = e[i].cap + e[i + 1].cap; // 總容量為正向邊和反向邊的容量之和
            x.flow = e[i + 1].cap;           // 流量等於反向邊的容量
            a.push_back(x);                  // 將邊新增到結果集中
        }
        return a; // 返回所有邊的資訊
    }
};
void solve()
{
    ll n;
    cin >> n;
    vl a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    // 建立一個最大流圖,節點總數為n個元素節點 + 60個位元位節點 + 源點和匯點
    MaxFlow<int> graph(n + 62);
    int s = n + 60; // 定義源點的編號為n + 60
    int t = s + 1;  // 定義匯點的編號為s + 1

    // 為每個元素節點新增邊連線到源點,容量為1,表示每個元素最多被選中一次
    for (int i = 0; i < n; i++)
    {
        graph.addEdge(s, i, 1);
        for (int j = 0; j < 60; j++) // 遍歷每個位元位
        {
            if (a[i] >> j & 1)                // 如果元素a[i]的第j位為1
                graph.addEdge(i, n + j, inf); // 從元素節點i到位元位節點(n + j)新增無限容量的邊
        }
    }

    // 為每個位元位節點新增邊連線到匯點,容量為1,表示每個位只能被一個元素覆蓋
    for (int j = 0; j < 60; j++)
    {
        graph.addEdge(n + j, t, 1);
    }

    // 計算最大流,即可以覆蓋的位元位的最大數量
    int max_flow = graph.flow(s, t);
    // 根據題意,最大子序列的值為n - 被覆蓋的位元位數量
    int ans = n - max_flow;
    cout << ans << "\n"; // 輸出答案
}

這場寫的太抽象了。

A連續四發wa1,幸好不加罰時,但還是浪費一堆時間。

B沒注意資料範圍,以為是\(2\times10^5\),我還在想啥場次這麼逆天把1500,1600的題放B的位置,幸好這種型別的dp題寫的多,直接搞出來了。

C題一下就想倒著列舉,但是邊界問題沒想明白,差點紅溫,於是決定果斷放棄,直接先寫D。

D不難想,一看就是很典型的題目。

E完全沒想到是網路流,亂搞了一下把樣例過了,還以為對了,結果wa2。

相關文章