ABC341

1|0D - Only one of two

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

数论,二分求第K大


LNM 的最小公倍数。

那么,有 XL 个不大于 X 的正整数能被 XL 整除,因此有 XN+XM2×XL 个整数 1X (含)能被 NM​ 中的一个整数整除。

此外,计数是随着 X 单调递增的,因此 "答案最多为 X "当且仅当" 1X 之间至少有 K 个整数正好能被 NM 中的一个整数整除",这又等价于 XN+XM2×XLK

因此,可以利用这一性质通过二分搜索来解决这个问题。在此问题的约束条件下,答案总是最多为 2×1018 ,因此在开始二进制搜索时,可以将下界和上界分别设为 02×1018

更具体地说,我们可以设置 L=0R=2×1018 ,并在 RL2 时重复下面的过程。

  1. X=L+R2 .
  2. 如果是 XN+XM2×XLK ,则设为 R=X ;否则,设为 L=X

答案就是结果 R

对于固定的 X ,可以在 O(1) 次内确定是否 XN+XM2×XLK ,迭代循环最多 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; }

2|0E - Alternating String

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

转化题意再用线段树维护

考虑线段树要维护什么信息


对于长度为 (N1) 的序列 A=(A1,A2,,AN1),如果 Si=Si+1,则令 Ai=0;如果 SiSi+1,则令 Ai=1​。

  • 那么,第一类型的查询 1 L R 会将 A 修改为 AL1(1AL1)AR(1AR)
    在这里,如果 L=1R=N,则前者或后者的更新是不必要的。

  • 另一方面,第二类型的查询 2 L RAL=AL+1==AR1=1,则输出 Yes,否则输出 No

    注意到每个 Ai 取值为 01,可以通过判断 AL+AL+1++AR1=RL​ 来决定输出是否为 Yes

这些操作可以通过线段树实现。

每种查询都可以在 O(logN) 的时间内完成,因此总共可以在 O(QlogN)​ 的时间内解决问题,这足够快速。

在实现上述算法时,需要注意当 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; }

3|0F - Breakdown

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

dp[v] 为从顶点 v 放置棋子开始,可执行的最大操作数,即顶点 v 上棋子的最大贡献。如果对于所有 v=1,2,,N 都找到了这个值,那么答案就是 v=1Ndp[v]×Av。以下是我们尝试用动态规划(DP)来找到 dp[] 的方法。

如果您从顶点 v 移除一个棋子,并且棋子放置在集合 S 中的顶点上,那么对于每个 uS 的顶点,可以执行 dp[u] 次操作,总共可以执行 uSdp[u] 次。因此,选择 S 以最大化 uSdp[u] 是足够的;换句话说,

(1)dp[v]=1+maxSuSdp[u].

这里,S 是任何可能为空的与 v 相邻的顶点集合,使得 uSWu<Wv

由于 S 只能包含 Wu<Wv 的顶点 u,所以方程(1)的右侧只由 Wu<Wv 的顶点 u 组成,因此可以按 Wv 的升序依次找到所有 vdp[v]

对于固定的 v,方程(1)的右侧可以视为以下背包问题:

对于与 v 相邻的每个顶点 u1,u2,,udeg(v)ui价值dp[ui],其 成本Wui。在总成本为 Wv 的约束下,最大化所选顶点的总价值。

这可以通过 DP 在 O(deg(v)×Wv) 时间内解决。

由于

v=1Ndeg(v)WvWmaxv=1Ndeg(v)=Wmax×2M,

其中 Wmax\coloneqqmax{W1,W2,,WN},因此可以总共在 O(MWmax) 时间内解决上述背包问题

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; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/18215275.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示