题解 | 2026牛客寒假算法基础集训营 5

个人难度评级

签到:$BGJ$

简单:$DEFI$

中等:$ACH$

A 智乃的二进制

不难发现,$10^a$ 都是好数:$10^a = 2^a \cdot 5^a$ ,所以它的二进制最后 $a$ 位都是 $0$ ,又因为 $5^a$ 是奇数,所以倒数第 $a+1$ 位一定是 $1$ 。

其次,对于 $10^a+10^b,a>b$ ,只要满足 $b\equiv 0\pmod{2^{a-b-1}}$ ,它就是好数:

如果满足好数定义,就有
$$X\equiv 2^a+2^b\pmod{2^{a+1}}$$
也就是
$$10^a+10^b\equiv 2^a+2^b\pmod{2^{a+1}}$$
然后化简一下,得到
$$2^{a-b}(5^a-1)+(5^b-1)\equiv 0\pmod {2^{a-b+1}}$$
也就是
$$5^b-1\equiv 0 \pmod {2^{a-b+1}}$$
现在我们要求 m 满足什么条件时,5^m−1 能被 2^{a-b+1} 整除。
我们展开得到
$$v_2(5^m-1) = v_2(m)+v_2(5-1) = v_2(m)+2$$
我们要 $v_2(5^m-1)\geq a-b+1$ ,也就是 $v_2(m)\geq a-b-1$ ,就能得到 $m$ 必须是 $2^{a-b-1}$​ 的倍数。

由以上结论也能发现,好数总是被 $10^a$ 分割成几个部分,直接计算一下还原即可。

void solve() {
    ll k;
    cin >> k;
    auto check = [&](ll x) {
        if (x < 0)
            return 0ll;
        ll s = 1 + 2 * x;
        for (int i = 1; i <= 62; ++i) {
            ll p = 1ll << (i - 1);
            if (p > x)
                break;
            s += (x - i) / p;
        }
        return s;
    };
    ll l = 0, r = 2e18, n = 0;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (check(mid) >= k)
            n = mid, r = mid - 1;
        else
            l = mid + 1;
    }
    ll R = k - check(n - 1);

    if (R == 1)
        cout << ksm(Z(10), n) << endl;
    else if (R == 2)
        cout << ksm(Z(10), n) + 1 << endl;
    else {
        vector<int> D;
        for (int i = 1; i <= 62; ++i) {
            if (i >= n)
                break;
            if ((n - i) % (1ll << (i - 1)) == 0)
                D.pb(i);
        }
        sort(rall(D));
        int d = D[R - 3];
        cout << ksm(Z(10), n) + ksm(Z(10), n - d) << endl;
    }
}

B 智乃的瓷砖

用一个标记记录一下每行的开头是什么,然后每行每一个都和前一个不一样即可。

void solve() {
    bool f1 = 0;
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if ((j & 1) == f1)
                cout << '/';
            else
                cout << '\\';
        }
        f1 = !f1;
        cout << endl;
    }
    cout << endl;
}

C 智乃的草坪

每个点在第 $t$ 秒的半径为 $R_i = v_i t$ 。对于位于 $(p_i,0)$ 的圆,当且仅当 $R_i \geq r$ 时,它在水平方向上存在能覆盖整段竖直区间 $[-r,r]$ 的投影区间。具体地,此时在 $x$ 轴上它能覆盖的整列 $x$ 满足
$$(x-p_i)^2\leq R_i^2-r^2$$
因此对应的区间为
$$[p_i-\sqrt{R_i^2-r^2},p_i+\sqrt{R_i^2-r^2}]$$
要覆盖整个矩形 $[0,c]\times[-r,r]$ ,等价于这些区间(从选定的至多 $k$ 个点产生)能覆盖段 $[0,c]$ 。

于是对给定的时间 $t$ ,我们可以把每个点能形成的区间计算出来(如果 $R_i<r$ 则忽略),得到若干区间。然后从当前位置 $cur=0$ 开始,每步在所有左端 $\le cur$ 的区间中取右端最远的一个,更新 $cur$ 到该最远右端,计数加 $1$ ;若超过 $k$ 次仍未覆盖到 $c$ 则失败。(也就是区间线段覆盖问题)

这个判定是显然单调的:时间越大,区间越宽,覆盖更容易,所以我们可以使用二分计算时间。

void solve() {
    int n, k;
    ld r, c;
    cin >> n >> k >> r >> c;
    vector<pair<ld, ld>> pts(n);
    for (int i = 0; i < n; ++i) {
        cin >> pts[i].fi >> pts[i].se;
    }

    auto check = [&](ld t) {
        vector<pair<ld, ld>> segs;
        for (auto [p, v] : pts) {
            ld R = v * t;
            if (R + eps < r)
                continue;
            ld h = sqrt(max(0.0l, R * R - r * r));
            ld L = p - h, Rr = p + h;
            segs.eb(L, Rr);
        }
        if (segs.empty())
            return false;
        sort(all(segs));
        ld cur = 0.0l;
        int used = 0, idx = 0;
        int m = segs.size();
        while (cur + eps < c && used < k) {
            ld mx = cur;
            while (idx < m && segs[idx].fi <= cur + eps) {
                if (segs[idx].se > mx)
                    mx = segs[idx].se;
                ++idx;
            }
            if (mx <= cur + eps)
                break;
            cur = mx;
            ++used;
        }
        return (cur + eps >= c);
    };

    ld lo = 0.0l, hi = INF, ans = hi;
    for (int it = 0; it < 100; ++it) {
        ld mid = (lo + hi) / 2.0l;
        if (check(mid))
            ans = mid, hi = mid;
        else
            lo = mid;
    }
    cout << ans << endl;
}

D 智乃的果子

直觉告诉我们肯定是从小往大合并会比较优。我们用优先队列记录一下果子的重量和数量,每次都取最轻的处理,把其中的一半两两合并;如果这一组只有一个果子,我们就再取一组与它合并。

void solve() {
    int n;
    ll tot = 0;
    cin >> n;
    priority_queue<PLL> pq;
    for (int i = 0; i < n; ++i) {
        ll c, w;
        cin >> c >> w;
        pq.push({-w, c});
        tot += c;
    }
    if (tot <= 1) {
        cout << 0 << endl;
        return;
    }
    Z ans = 0;
    while (tot > 1) {
        auto [w, c] = pq.top();
        pq.pop();
        w = -w;
        if (c >= 2) {
            ll P = c / 2;
            ans += Z(2) * w * P;
            pq.push({-w * 2, P});
            if (c & 1)
                pq.push({-w, 1});
            tot -= P;
        } else {
            auto [tw, tc] = pq.top();
            pq.pop();
            tw = -tw, tc--;
            ans += w + tw;
            pq.push({-w - tw, 1});
            if (tc)
                pq.push({-tw, tc});
            tot--;
        }
    }
    cout << ans << endl;
}

E 智乃的最大子段和取模

模意义下也可以进行前缀和的操作,$[l,r]$ 的子段和为 $(pre[r+1]-pre[l]+p)\% p$ ,所以对于每个 $j=r+1$ ,我们都需要找一个 $t$ 使得 $(pre[r+1]-pre[l]+p)\% p$ 最大即可。

如果 $pre[j]\geq pre[t]$ ,那么找最小 $pre[t],t\leq j$ 就行;如果 $pre[j]<pre[t]$ ,也就是原式为 $p-(pre[t]-pre[j])$ ,所以我们要取最接近 $pre[j]$ 的 $pre[j]$ 。所以对每一个 $j$ 检查这两类即可。

void solve() {
    int n, p;
    cin >> n >> p;
    vector<int> a(n);
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    vector<ll> pre(n + 1);
    for (int i = 1; i <= n; ++i) {
        pre[i] = (pre[i - 1] + a[i - 1]) % p;
    }
    set<PLL> s;
    s.insert({0, 0});
    ll mx = -1;
    int L = 0, R = 0;

    for (int j = 1; j <= n; ++j) {
        ll cur = pre[j];

        if (s.begin() != s.end()) {
            ll val = (cur - s.begin()->fi + p) % p;
            if (val > mx) {
                mx = val;
                L = s.begin()->se;
                R = j - 1;
            }
        }

        auto it = s.upper_bound({cur, INF});
        if (it != s.end()) {
            ll val = (cur - it->fi + p) % p;
            if (val > mx) {
                mx = val;
                L = it->se;
                R = j - 1;
            }
        }

        s.insert({cur, j});
    }

    cout << L << " " << R << " " << mx << endl;
}

F 智乃的算法竞赛群友

我们设 $x$ 为 $\text{qcjjkkt}$ 的个数, $y$ 为 $\text{td}$ 的个数,当一个 $\text{td}$ 使用 $\text{qcjjkkt}$ 末尾的 $\text{t}$ 的时候,我们就能节省一个字符。

所以:

  • $x\leq y$ ,最小的长度为 $6x+2y$ 。
  • $x>y$ ,最小的长度为 $7x+y$ 。

我们要求的就是满足最小长度 $\leq n$ 的 $ax+by$ ,对于固定的 $x$ ,我们只需要检查几个可能取到最值的 $y$ 即可,也就是 ${0,\lfloor\frac{n}{8}\rfloor,\lfloor\frac{n}{7}\rfloor}$ 以及周围的一些点。

constexpr ll INF = 2e18;

void solve() {
    ll n, a, b;
    cin >> n >> a >> b;
    vector<ll> X;
    X.pb(0);
    auto check = [&](ll x) {
        return 0 <= x && x <= n;
    };
    for (int i = -3; i <= 3; ++i) {
        if (check(n / 8 + i))
            X.pb(n / 8 + i);
        if (check(n / 7 + i))
            X.pb(n / 7 + i);
        if (check((n + 1) / 8 + i))
            X.pb((n + 1) / 8 + i);
    }
    for (int i = 0; i <= 6; ++i)
        if (check(i))
            X.pb(i);
    sort(all(X));
    X.erase(unique(all(X)), X.end());

    auto calc = [&](ll x) {
        ll mx = -1;
        if (x <= n / 8) {
            ll t = (n - 6 * x) / 2;
            if (t >= x) {
                mx = t;
            } else {
                if (n - 7 * x >= 0) {
                    mx = max(x - 1, n - 7 * x);
                } else {
                    return -INF;
                }
            }
        } else {
            if (n - 7 * x >= 0) {
                mx = min(x - 1, n - 7 * x);
            } else {
                return -INF;
            }
        }
        if (mx < 0)
            return -INF;
        return a * x + b * mx;
    };

    ll ans = 0;
    bool f = 0;
    for (auto x : X) {
        ll val = calc(x);
        if (val == -INF)
            continue;
        if (!f || val > ans) {
            ans = val;
            f = 1;
        }
    }
    if (!f)
        cout << 0 << endl;
    else
        cout << ans << endl;
}

G 智乃的箭头魔术

模拟一下即可,模拟的代码如下

void solve() {
    string s = "0112233445142015320125410214530214510214102302142025101203201451451522302514203214510021454101002532";
    int c = 0;
    for (auto v : s) {
        if (v == '0')
            c = 3 - c;
        else if (v == '1') {
            if (c == 3)
                c = 1;
            else if (c == 1)
                c = 3;
        } else if (v == '2') {
            if (c == 0)
                c = 1;
            else if (c == 1)
                c = 0;
            else if (c == 3)
                c = 2;
            else if (c == 2)
                c = 3;
        } else if (v == '3') {
            if (c == 0)
                c = 2;
            else if (c == 2)
                c = 0;
        } else if (v == '4') {
            c = (c + 1) % 4;
        } else if (v == '5') {
            c = (c + 3) % 4;
        }
        cout << c;
    }
}

答案为

3132333010010310230010130130330130312312210210010321300120122322322101123223211001003013030031210332

H 智乃的矩阵

首先我们发现,进行一次操作之后,所有元素之和不变,所以最开始元素之和一定要为 $n^2$ 的倍数,我们令 $T = tot/n^2$。

其次我们发现,对于每种操作,如果把矩阵染成黑白格,即 $i+j$ 为奇数的格子染黑,$i+j$ 为偶数的格子染白,那么对于同一种颜色,每种操作都不会改变这个颜色的元素之和,所以令颜色的格子数为 $cnt$ ,该颜色的元素之和为 $sum$ ,任何一种颜色都要满足 $sum=cnt\times T$。

然后我们发现,在模 2 的意义下,每次操作(无论是加还是减)相当于给选定的 $2\times 2$ 区域内的 4 个数都 $+1$(因为 $1\equiv -1\pmod 2$ )。所以我们需要判断是否能通过若干次 $2\times 2$ 区域的翻转,将矩阵 $B$(其中 $B_{i,j}=(A_{i,j}−T)\pmod 2$ )变成全 $0$ 矩阵。我们用贪心或者高斯消元都可以解决,这里给出贪心的解法。

设 $X_{i,j}$ 为模 $2$ 意义下以 $(i,j)$ 为左上角的操作次数。对于位置 $(1,1)$,只有操作 $X_{1,1}$ 能影响它。为了使 $B_{1,1}$ 变为 $0$ ,必须有 $X_{1,1}=B_{1,1}$ 。对于位置 $(i,j)$ ,它受到 $X_{i,j}$ 、$X_{i−1,j}$ 、$X_{i,j−1}$ 、$X_{i−1,j−1}$ 的影响。当我们遍历到 $(i,j)$ 时,后三者已经确定,因此 $X_{i,j}$ 被唯一确定。计算出所有 $1≤i,j<n$ 的 $X_{i,j}$ 后,还需要检查矩阵的边界(第 $n$ 行和第 $n$ 列)是否也满足变成了 0。如果不满足,则无法达成。

void solve() {
    int n;
    ll tot = 0, sum = 0, cnt = 0;
    cin >> n;
    vector<vector<int>> a(n + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            cin >> a[i][j], tot += a[i][j], sum += (i + j & 1) * a[i][j], cnt += i + j & 1;
    if (n == 1) {
        cout << "Yes" << endl;
        return;
    }
    if (tot % (n * n) != 0) {
        cout << "No" << endl;
        return;
    }
    ll T = tot / (n * n);
    if (sum != cnt * T) {
        cout << "No" << endl;
        return;
    }
    vector<vector<int>> b(n + 2, vector<int>(n + 2));
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            b[i][j] = ((a[i][j] - T) % 2 + 2) % 2;
        }
    }
    vector<vector<int>> x(n + 2, vector<int>(n + 2));
    for (int i = 1; i < n; ++i) {
        for (int j = 1; j < n; ++j) {
            x[i][j] = b[i][j] ^ x[i - 1][j] ^ x[i][j - 1] ^ x[i - 1][j - 1];
        }
    }

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            int cur = 0;
            if (i < n && j < n)
                cur ^= x[i][j];
            if (i > 1 && j < n)
                cur ^= x[i - 1][j];
            if (i < n && j > 1)
                cur ^= x[i][j - 1];
            if (i > 1 && j > 1)
                cur ^= x[i - 1][j - 1];

            if (cur != b[i][j]) {
                cout << "No" << endl;
                return;
            }
        }
    }

    cout << "Yes" << endl;
}

I 智乃挖坑

首先怎么去判定被挖穿,直接模拟肯定会超时,所以我们考虑使用差分。对于每一个坑我们都可以拆成两部分,左半深度增加,右半深度减小,我们维护这两个差分数组,然后做前缀和就可以检查有没有超过 $h$ 了。

那么对于整个时间轴,肯定能找到一个时间点,前面都没被挖穿,后面都被挖穿了,所以我们使用二分去计算次数,每次 $\text{check}$ 的时候模拟一下即可。

void solve() {
    int n, m;
    ll h;
    cin >> n >> m >> h;
    vector<PII> ops(m);
    for (int i = 0; i < m; ++i) {
        cin >> ops[i].fi >> ops[i].se;
    }
    auto check = [&](int t) {
        vector<ll> A(n + 2), B(n + 2);
        for (int i = 0; i < t; ++i) {
            auto [p, f] = ops[i];
            int L = max(1, p - (f - 1)), R = min(n, p + (f - 1));
            int l1 = L, r1 = min(p, R);
            if (l1 <= r1) {
                A[l1] += 1, A[r1 + 1] -= 1;
                B[l1] += f - p, B[r1 + 1] -= f - p;
            }
            int l2 = max(p + 1, L), r2 = R;
            if (l2 <= r2) {
                A[l2] += -1, A[r2 + 1] -= -1;
                B[l2] += f + p, B[r2 + 1] -= f + p;
            }
        }
        ll a = 0, b = 0;
        for (int j = 1; j <= n; ++j) {
            a += A[j];
            b += B[j];
            if (a * j + b > h)
                return 1;
        }
        return 0;
    };

    if (!check(m)) {
        cout << "No" << endl;
        return;
    }

    int lo = 1, hi = m, ans = m;
    while (lo <= hi) {
        int mid = (lo + hi) >> 1;
        if (check(mid)) {
            ans = mid;
            hi = mid - 1;
        } else
            lo = mid + 1;
    }
    cout << "Yes" << endl;
    cout << ans << endl;
}

J 智乃的幻方

暴力检查一下即可。

void solve() {
    vector<vector<int>> a(3, vector<int>(3));
    unordered_set<int> st;
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            cin >> a[i][j];
            st.insert(a[i][j]);
        }
    }
    if (st.size() != 9) {
        cout << "No" << endl;
        return;
    }
    int S = a[0][0] + a[0][1] + a[0][2];
    for (int i = 0; i < 3; ++i) {
        int s = 0;
        for (int j = 0; j < 3; ++j) {
            s += a[i][j];
        }
        if (s != S) {
            cout << "No" << endl;
            return;
        }
    }
    for (int i = 0; i < 3; ++i) {
        int s = 0;
        for (int j = 0; j < 3; ++j) {
            s += a[j][i];
        }
        if (s != S) {
            cout << "No" << endl;
            return;
        }
    }
    if (a[0][0] + a[1][1] + a[2][2] != S) {
        cout << "No" << endl;
        return;
    }
    if (a[2][0] + a[1][1] + a[0][2] != S) {
        cout << "No" << endl;
        return;
    }
    cout << "Yes" << endl;
}

头文件

#include <bits/stdc++.h>
#define pb push_back
#define pf push_front
#define pob pop_back
#define pof pop_front
#define eb emplace_back
#define fi first
#define se second
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define endl "\n"
using namespace std;

using ll = long long;
using ld = long double;
using ui = unsigned;
using ull = unsigned long long;
using i128 = __int128;
using PII = pair<int, int>;
using PLL = pair<ll, ll>;
using TIII = tuple<int, int, int>;
using TLLL = tuple<ll, ll, ll>;

void init() {
}

void solve() {
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    init();

    int t = 1;
    cin >> t;
    cout << fixed << setprecision(15);
    for (int _ = 1; _ <= t; ++_) {
        solve();
    }

    return 0;
}

自动取模

constexpr int MOD = 1e9+7;

template <class T>
constexpr T ksm(T a, ll b) {
    T res = 1;
    while (b) {
        if (b & 1)
            res *= a;
        b >>= 1;
        a *= a;
    }
    return res;
}

template <int P>
struct MInt {
    int x;
    constexpr MInt() : x() {}
    constexpr MInt(ll x) : x{norm(x % getMod())} {}
    static int Mod;
    constexpr static int getMod() {
        if (P > 0) {
            return P;
        } else {
            return Mod;
        }
    }
    constexpr static void setMod(int Mod_) {
        Mod = Mod_;
    }
    constexpr int norm(int x) const {
        if (x < 0) {
            x += getMod();
        }
        if (x >= getMod()) {
            x -= getMod();
        }
        return x;
    }
    constexpr int val() const {
        return x;
    }
    explicit constexpr operator int() const {
        return x;
    }
    constexpr MInt operator-() const {
        MInt res;
        res.x = norm(getMod() - x);
        return res;
    }
    constexpr MInt inv() const {
        assert(x != 0);
        return ksm(*this, getMod() - 2);
    }
    constexpr MInt &operator*=(MInt rhs) & {
        x = 1LL * x * rhs.x % getMod();
        return *this;
    }
    constexpr MInt &operator+=(MInt rhs) & {
        x = norm(x + rhs.x);
        return *this;
    }
    constexpr MInt &operator-=(MInt rhs) & {
        x = norm(x - rhs.x);
        return *this;
    }
    constexpr MInt &operator/=(MInt rhs) & {
        return *this *= rhs.inv();
    }
    friend constexpr MInt operator*(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res *= rhs;
        return res;
    }
    friend constexpr MInt operator+(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res += rhs;
        return res;
    }
    friend constexpr MInt operator-(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res -= rhs;
        return res;
    }
    friend constexpr MInt operator/(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res /= rhs;
        return res;
    }
    friend constexpr istream &operator>>(istream &is, MInt &a) {
        ll v;
        is >> v;
        a = MInt(v);
        return is;
    }
    friend constexpr ostream &operator<<(ostream &os, const MInt &a) {
        return os << a.val();
    }
    friend constexpr bool operator==(MInt lhs, MInt rhs) {
        return lhs.val() == rhs.val();
    }
    friend constexpr bool operator!=(MInt lhs, MInt rhs) {
        return lhs.val() != rhs.val();
    }
};

template <>
int MInt<0>::Mod = 998244353;

template <int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();

using Z = MInt<MOD>;
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇