做题记录3

CF2149E. Hidden Knowledge of the Ancients

思路

滑动窗口 + 双指针。

先不考虑长度的限制,求"恰好有 \(k\) 个不同的数"的区间。可以维护两个窗口,一个是以当前的位置为右端点,且第一个最多有 \(k\) 个不同元素 的区间;一个是以当前位置为右端点,且第一个 最多有 \(k - 1\) 个不同元素 的区间。那么以当前位置为右端点,且恰好有 \(k\) 个不同元素的子区间的数量就为 最多 \(k - 1\) 个 - 最多 \(k\)。其实就是它们的左端点相减的结果。再加上长度限制,要满足

\[l \leq (右端点 - 左端点 + 1) \leq r \Rightarrow 右 - r + 1 \leq 左 \leq 右 - l + 1 \]

所以对于当前的右端点,合法的左端点的数量就为 \(\min(最多 k - 1 个的左端点, c - l + 1) - \max(最多 k 个的右端点, c - r + 1)\)

由于 \(a\) 的范围很大,所以要先离散化。

代码

void solve(void) {
    int n, k, l, r;
    std::cin >> n >> k >> l >> r;
    std::vector<int> a(n);
    for(int i = 0; i < n; i++) std::cin >> a[i];
    auto tmp = a;
    std::sort(tmp.begin(), tmp.end());
    tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
    for(auto &i : a) {
        i = std::lower_bound(tmp.begin(), tmp.end(), i) - tmp.begin();
    }
    int len  = tmp.size();
    i64 ans = 0;
    std::vector<int> K(len), K1(len);
    int disk = 0, disk1 = 0;
    int lk = 0, lk1 = 0;
    for(int i = 0; i < n; i++) {
        K[a[i]]++;
        if(K[a[i]] == 1) {
            disk++;
        }
        while(disk > k) {
            int x = a[lk++];
            K[x]--;
            if(K[x] == 0) {
                disk--;
            }
        }
        K1[a[i]]++;
        if(K1[a[i]] == 1) {
            disk1++;
        }
        while(disk1 > k - 1) {
            int x = a[lk1++];
            K1[x]--;
            if(K1[x] == 0) {
                disk1--;
            }
        }
        int L = std::max(lk, i - r + 1);
        int R = std::min(lk1 - 1, i - l + 1);
        if(R >= L) ans += R - L + 1;
    }
    std::cout << ans << '\n';
}

CF2149F. Nezuko in the Clearing

思路

二分。

首先注意到,无论如何都是要走 \(d\) 个回合的,但是在这期间可能会休息若干次。因此可以表示为将 \(d\) 个回合分割为了 \(n\) 段,每段连续走 \(k_i\) 个回合,然后停下来休息一次,最后的答案就应该是:

\[d + (n - 1) \]

在这期间,血量的总消耗是:

\[\sum_{i = 1}^{n}\frac{k_i(k_i + 1)}{2} - (n - 1) \]

要保证消耗 \(\leq h - 1\) ,这样才能保证每一次触发的时候都有 \(\geq 1\) 的血量。

显然我们需要枚举分段的数量,如何 check 呢?

观察函数 \(f(k) = \frac{k(k + 1)}{2}\) ,我们想要让 \(\sum_{i = 1}^{n} f(k_i)\) 最小,注意到应该让每一段的长度都尽可能的接近,也就是说我们会分成 \(d \mod n\)\(\lfloor \frac{d}{n} \rfloor + 1\) ,剩下的 $\lfloor \frac{d}{n} \rfloor $ ,然后就可以判断了。

但是如何证明这个结论呢?我不会

代码

void solve(void) {
    i64 h, d;
    std::cin >> h >> d;
    if(h == 1) {
        std::cout << d * 2 << '\n';
        return;
    }
    i64 l = 1, r = d, len = -1;
    auto check = [&](i64 x) -> bool {
        i64 q = d / x;
        i64 r = d % x;
        i64 sum = r * (q + 1) * (q + 2) / 2;
        i64 sum1 = (x - r) * q * (q + 1) / 2;
        i64 total = sum + sum1 - (x - 1);
        return total <= h - 1;
    };
    while(l <= r) {
        i64 mid = (l + r) / 2;
        if(check(mid)) {
            r = mid - 1;
            len = mid;
        } else l = mid + 1;
    }
    //std::cout << len << '\n';
    std::cout << (len - 1) + d << '\n';
}

牛客周赛 Round 111 D-小红的好数对

思路

注意到 \(11\) 的倍数有如下规律:奇数位之和减去偶数为之和是 \(11\) 的倍数。

两个数 \(A\)\(B\) 拼接的结果是 \(A \times 10^{\lg B + 1} + B\) ,因为 \(10 \equiv -1 \pmod{11}\) , 要想让这个结果是 \(11\) 的倍数,有:

\[(A \bmod 11)(-1)^{\lg B + 1} + (B \bmod 11) \equiv 0 \pmod{11} \]

所以可以统计每个数字 \(\bmod 11\) 的结果。之后对于每一个数字 \(i\) ,当它作为第二个数时,如果是奇数位,那么能和它凑的数字的余数

\[r_A \equiv \left\{\begin{matrix} -r_i \pmod{11} & \text{长度是奇数} \\ r_i \pmod{11} & \text{长度是偶数} \end{matrix}\right. \]

然后就可以直接统计了。最后特判一下数字自己能和自己匹配的情况。

代码

void solve(void) {
    int n; std::cin >> n;
    std::vector<int> a(n + 1), r(n + 1), p(n + 1);
    for(int i = 1; i <= n; i++) {
        std::cin >> a[i];
        r[i] = a[i] % 11;
        int len = (int)std::to_string(a[i]).size();
        p[i] = len & 1;
    }
    std::vector<int> cnt(11);
    for(int i = 1; i <= n; i++) {
        cnt[r[i]]++;
    }
    i64 ans = 0;
    for(int i = 1; i <= n; i++) {
        int x = 0;
        if(p[i]) x = r[i];
        else x = (11 - r[i]) % 11;
        ans += cnt[x];
        if(x == r[i]) ans--;
    }
    std::cout << ans << '\n';
}

CF766D. Mahmoud and a Dictionary

思路

扩展域并查集板子。

设单词 \(a + len\) 的翻译词为 \(a + len\) 。这样就可以做如下合并:

如果单词 \(a\)\(b\) 是同义词,就合并 \((a, b)\)\((a + len, b + len)\)

如果是反义词,就合并 \((a + len, b)\)\((a, b + len)\)

每次合并操作前,先判断 \(a\)\(b\) 之间是否已经存在了矛盾关系然后输出,接下来的 \(q\) 次询问直接判断所属集合就好了。

为了方便,可以做一个字符串和数字的映射。

代码

struct DSU {
    std::vector<int> p, siz;
    DSU(int n): p(n + 1), siz(n + 1, 1) {
        std::iota(p.begin(), p.end(), 0);
    }
    int find(int x) {
        if(x == p[x]) return p[x];
        return p[x] = find(p[x]);
    }
    void unite(int a, int b) {
        int pa = find(a), pb = find(b);
        if(pa == pb) return;
        if(siz[pa] < siz[pb]) std::swap(pa, pb);
        p[pb] = pa;
        siz[pa] += siz[pb];
    }
    bool same(int u, int v) {return find(u) == find(v);}
    int size(int u) {return siz[find(u)];}
};

void solve(void) {
    int n, m, q;
    std::cin >> n >> m >> q; 
    std::vector<std::string> a(n);
    for(int i = 0; i < n; i++) std::cin >> a[i];
    auto tmp = a;
    std::sort(tmp.begin(), tmp.end());
    tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
    int len = (int)tmp.size();
    DSU dsu(len + len);
    for(int i = 1; i <= m; i++) {
        int op; std::string aa, bb;
        std::cin >> op >> aa >> bb;
        int x = std::lower_bound(tmp.begin(), tmp.end(), aa) - tmp.begin();
        int y = std::lower_bound(tmp.begin(), tmp.end(), bb) - tmp.begin();
        if(op == 1) {
            if(dsu.same(x, y + len)) {
                std::cout << "NO\n";
            } else {
                std::cout << "YES\n";
                dsu.unite(x, y);
                dsu.unite(x + len, y + len);
            }
        } else {
            if(dsu.same(x, y)) {
                std::cout << "NO\n";
            } else {
                std::cout << "YES\n";
                dsu.unite(x + len, y);
                dsu.unite(x, y + len);
            }
        }
    }
    for(int i = 1; i <= q; i++) {
        std::string aa, bb;
        std::cin >> aa >> bb;
        int x = std::lower_bound(tmp.begin(), tmp.end(), aa) - tmp.begin();
        int y = std::lower_bound(tmp.begin(), tmp.end(), bb) - tmp.begin();
        if(dsu.same(x, y)) {
            std::cout << "1\n";
        } else if(dsu.same(x, y + len)) {
            std::cout << "2\n";
        } else std::cout << "3\n";
    }
}

posted @ 2025-09-26 00:27  dbywsc  阅读(53)  评论(0)    收藏  举报