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\) 時重複下面的過程。
- 讓 \(X=\lfloor \frac{L+R}{2}\rfloor\) .
- 如果是 \(\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]\) 是足夠的;換句話說,
這裡,\(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)\) 時間內解決。
由於
其中 \(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;
}