ABC341

加固文明幻景發表於2024-05-27

D - Only one of two

https://atcoder.jp/contests/abc341/tasks/abc341_d

數論,二分求第K大


\(L\)\(N\)\(M\) 的最小公倍數。

那麼,有 \(\lfloor \frac{X}{L}\rfloor\) 個不大於 \(X\) 的正整數能被 \(\lfloor \frac{X}{L}\rfloor\) 整除,因此有 \(\lfloor \frac{X}{N}\rfloor+\lfloor \frac{X}{M}\rfloor-2\times \lfloor \frac{X}{L}\rfloor\) 個整數 \(1\)\(X\) (含)能被 \(N\)\(M\)​ 中的一個整數整除。

此外,計數是隨著 \(X\) 單調遞增的,因此 "答案最多為 \(X\) "當且僅當" \(1\)\(X\) 之間至少有 \(K\) 個整數正好能被 \(N\)\(M\) 中的一個整數整除",這又等價於 \(\lfloor \frac{X}{N}\rfloor+\lfloor \frac{X}{M}\rfloor-2\times \lfloor \frac{X}{L}\rfloor\geq K\)

因此,可以利用這一性質透過二分搜尋來解決這個問題。在此問題的約束條件下,答案總是最多為 \(2\times 10^{18}\) ,因此在開始二進位制搜尋時,可以將下界和上界分別設為 \(0\)\(2\times 10^{18}\)

更具體地說,我們可以設定 \(L=0\)\(R=2\times 10^{18}\) ,並在 \(R-L\geq 2\) 時重複下面的過程。

  1. \(X=\lfloor \frac{L+R}{2}\rfloor\) .
  2. 如果是 \(\lfloor \frac{X}{N}\rfloor+\lfloor \frac{X}{M}\rfloor-2\times \lfloor \frac{X}{L}\rfloor\geq K\) ,則設為 \(R=X\) ;否則,設為 \(L=X\)

答案就是結果 \(R\)

對於固定的 \(X\) ,可以在 \(O(1)\) 次內確定是否 \(\lfloor \frac{X}{N}\rfloor+\lfloor \frac{X}{M}\rfloor-2\times \lfloor \frac{X}{L}\rfloor\geq K\) ,迭代迴圈最多 \(60\)​ 次。因此,問題已經解決。

signed main() {

    std::cin.tie(nullptr)->sync_with_stdio(false);

    i64 n, m, k;
    std::cin >> n >> m >> k;
    i64 lo = 0, hi = 2E18;
    i64 L = std::lcm(n, m);
    while (lo <= hi) {
        i64 mid = lo + hi >> 1;
        if ((mid / n) + (mid / m) - 2 * (mid / L) < k) {
            lo = mid + 1;
        } else {
            hi = mid - 1;
        }
    }

    std::cout << hi + 1 << '\n';

    return 0;
}

E - Alternating String

https://atcoder.jp/contests/abc341/tasks/abc341_e

轉化題意再用線段樹維護

考慮線段樹要維護什麼資訊


對於長度為 \((N-1)\) 的序列 \(A=(A_1,A_2,\ldots,A_{N-1})\),如果 \(S_i=S_{i+1}\),則令 \(A_i=0\);如果 \(S_i\neq S_{i+1}\),則令 \(A_i=1\)​。

  • 那麼,第一型別的查詢 1 L R 會將 \(A\) 修改為 \(A_{L-1}\leftarrow (1-A_{L-1})\)\(A_R\leftarrow (1-A_R)\)
    在這裡,如果 \(L=1\)\(R=N\),則前者或後者的更新是不必要的。

  • 另一方面,第二型別的查詢 2 L R\(A_L=A_{L+1}=\cdots=A_{R-1}=1\),則輸出 Yes,否則輸出 No

    注意到每個 \(A_i\) 取值為 \(0\)\(1\),可以透過判斷 \(A_L+A_{L+1}+\cdots+A_{R-1}=R-L\)​ 來決定輸出是否為 Yes

這些操作可以透過線段樹實現。

每種查詢都可以在 \(O(\log N)\) 的時間內完成,因此總共可以在 \(O(Q\log N)\)​ 的時間內解決問題,這足夠快速。

在實現上述演算法時,需要注意當 \(N=1\) 時可能需要的異常處理,此時 \(A\) 的長度為 \(0\)。可以編寫異常處理程式,或者分配稍長的 \(A\)

signed main() {

    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n, m;
    std::cin >> n >> m;
    std::string s;
    std::cin >> s;

    SegmentTree<Info> T(n);
    for (int i = 0; i + 1 < n; i++) {
        if (s[i] != s[i + 1]) {T.modify(i, 1);}
        else {T.modify(i, 0);}
    }

    for (int i = 0, x, l, r; i < m; i++) {
        std::cin >> x >> l >> r; --l;
        if (x == 1) {
            if (l > 0) {T.modify(l - 1, 1 - T.rangeQuery(l - 1, l).sum);}
            if (r < n) {T.modify(r - 1, 1 - T.rangeQuery(r - 1, r).sum);}//注意這裡是r - 1,因為r是開區間
        } else {
            std::cout << (T.rangeQuery(l, r - 1).sum == r - l - 1 ? "Yes" : "No") << '\n';
        }
    }

    return 0;
}

F - Breakdown

https://atcoder.jp/contests/abc341/tasks/abc341_f

\(\mathrm{dp}[v]\) 為從頂點 \(v\) 放置棋子開始,可執行的最大運算元,即頂點 \(v\) 上棋子的最大貢獻。如果對於所有 \(v = 1, 2, \ldots, N\) 都找到了這個值,那麼答案就是 \(\sum_{v =1}^N \mathrm{dp}[v] \times A_v\)。以下是我們嘗試用動態規劃(DP)來找到 \(\mathrm{dp}[\ast]\) 的方法。

如果您從頂點 \(v\) 移除一個棋子,並且棋子放置在集合 \(S\) 中的頂點上,那麼對於每個 \(u \in S\) 的頂點,可以執行 \(\mathrm{dp}[u]\) 次操作,總共可以執行 \(\sum_{u \in S} \mathrm{dp}[u]\) 次。因此,選擇 \(S\) 以最大化 \(\sum_{u \in S} \mathrm{dp}[u]\) 是足夠的;換句話說,

\[\mathrm{dp}[v] = 1 + \max_S \sum_{u \in S} \mathrm{dp}[u] \tag{1}. \]

這裡,\(S\) 是任何可能為空的與 \(v\) 相鄰的頂點集合,使得 \(\sum_{u \in S} W_u < W_v\)

由於 \(S\) 只能包含 \(W_u < W_v\) 的頂點 \(u\),所以方程(1)的右側只由 \(W_u < W_v\) 的頂點 \(u\) 組成,因此可以按 \(W_v\) 的升序依次找到所有 \(v\)\(\mathrm{dp}[v]\)

對於固定的 \(v\),方程(1)的右側可以視為以下揹包問題:

對於與 \(v\) 相鄰的每個頂點 \(u_1, u_2, \ldots, u_{\deg(v)}\)\(u_i\)價值\(\mathrm{dp}[u_i]\),其 成本\(W_{u_i}\)。在總成本為 \(W_v\) 的約束下,最大化所選頂點的總價值。

這可以透過 DP 在 \(O(\deg(v) \times W_v)\) 時間內解決。

由於

\[\sum_{v = 1}^N \deg(v) W_v \leq W_{\max} \sum_{v = 1}^N \deg(v) = W_{\max} \times 2M, \]

其中 \(W_{\max} \coloneqq \max \lbrace W_1, W_2, \ldots, W_N \rbrace\),因此可以總共在 \(O(MW_{\max})\) 時間內解決上述揹包問題

signed main() {
    int N, M;
    std::cin >> N >> M;

    std::vector adj(N, std::vector<int>());
    for (int i = 0, u, v; i < M; i++) {
        std::cin >> u >> v; --u; --v;
        adj[u].push_back(v); adj[v].push_back(u);
    }

    std::vector<int> W(N), A(N);
    for (auto& x : W) {std::cin >> x;}
    for (auto& x : A) {std::cin >> x;}
    std::vector<int> p(N);//維護點權對應的下標
    std::iota(all(p), 0); 
    std::sort(all(p), [&](int i, int j){return W[i] < W[j];});
    std::vector<int> dp(N);
    for (auto& x : p) {//按照W[i]升序更新
        std::vector<int> f(W[x]);//找到當前容積下能放入的最大點數
        for (auto& y : adj[x]) if (W[y] < W[x]) {
            for (int i = W[x] - 1; i >= W[y]; i--) {
                f[i] = std::max(f[i], f[i - W[y]] + dp[y]);
            }
        }
        dp[x] = 1 + f[W[x] - 1];
    }

    i64 ans = 0;
    for (int i = 0; i < N; i++) {ans += 1LL * A[i] * dp[i];}

    std::cout << ans << '\n';

    return 0;
}