ABC341

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

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

F - 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;
}
posted @   加固文明幻景  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示