小白月赛 Round 132 题解

由于牛客的渲染问题,你可以点此链接进入我的博客查看

A 出题需要 rating

知识点:模拟

模拟一下分数变化即可。

时间复杂度

void solve() {
    int n, c = 1000;
    cin >> n;
    vector<int> s(7);
    for (int i = 0, x; i < n; ++i) {
        cin >> x;
        c += x;
        if (c < 700)
            s[0]++;
        else if (c < 1100)
            s[1]++;
        else if (c < 1500)
            s[2]++;
        else if (c < 2000)
            s[3]++;
        else if (c < 2400)
            s[4]++;
        else if (c < 2800)
            s[5]++;
        else
            s[6]++;
    }
    for (int i = 0; i < 7; ++i) cout << s[i] << " \n"[i == 6];
}

B 出题需要语文

知识点:贪心、模拟

每套题必须包含 各一道,因此每个难度之间互不影响。为了让平均质量尽可能高,对于每个难度,只需要选择质量分不低于 的题中质量最高的一道。

设最终选出的 道题质量和为 ,平均质量不低于 等价于 。如果某个难度没有可选题,或者每个难度都选最优后仍然 ,则无解;否则输出这 道题的编号即可。

时间复杂度

void solve() {
    int n;
    cin >> n;
    vector<PII> a(6);
    for (int i = 1; i <= n; ++i) {
        char c;
        int x;
        cin >> c >> x;
        if (x >= 60 && x > a[c - 'A'].fi) a[c - 'A'] = {x, i};
    }
    int sum = 0;
    for (auto [u, v] : a) {
        if (!u) {
            cout << -1 << endl;
            return;
        }
        sum += u;
    }
    if (sum < 420) {
        cout << -1 << endl;
        return;
    }
    for (auto [u, v] : a) cout << v << " ";
    cout << endl;
}

C 出题需要加法

知识点:二进制、前缀计数

可合数形如 。由于二进制表示唯一,当 时结果的二进制中恰好有两个 ;当 时结果为 ,也对应唯一的一种合成方式。因此只需枚举所有 ,统计不超过某个上界的可合数个数。

表示 中可合数的数量,则答案为:

枚举时令 ,避免重复统计同一个数,枚举到 ​ 即可。

时间复杂度

void solve() {
    int l, r;
    cin >> l >> r;
    auto calc = [&](int n) {
        int res = 0;
        for (int i = 0; i <= 62; ++i) {
            for (int j = 0; j <= i; ++j) {
                int x = (1LL << i) + (1LL << j);
                if (x <= n) ++res;
            }
        }
        return res;
    };
    cout << calc(r) - calc(l - 1) << endl;
}

当然也可以预处理所有可合数,然后二分查找。

时间复杂度

vector<int> vals;
 
void init() {
    for (int i = 0; i <= 62; ++i) {
        for (int j = 0; j <= i; ++j) {
            int val = (1LL << i) + (1LL << j);
            vals.pb(val);
        }
    }
    sort(vals);
}
 
void solve() {
    int l, r;
    cin >> l >> r;
    auto calc = [&](int n) { return upper_bound(all(vals), n) - vals.begin(); };
    cout << calc(r) - calc(l - 1) << endl;
}

D 出题需要构造

知识点:构造、循环移位

一次操作选出 个格子的值,实际会把这些值在矩阵中出现的所有位置都放入小球。因此我们只需要构造一个矩阵,使得选定的若干个数字出现的位置在每行、每列中都恰好有 个。

,总小球数从行看为 ,从列看为 ,必须相等,因此无解。若 ,一行一列不可能都有 个小球,也无解。

时,直接全填 ,选择数字 ,每行每列都有 个。

​ 时,构造循环矩阵:

每个数字在每行、每列中都恰好出现一次,因此选择任意两个不同数字即可让每行、每列恰好有 ​ 个小球。

时间复杂度

void solve() {
    int n, m;
    cin >> n >> m;

    if (n != m || n < 2) {
        cout << "NO" << endl;
        return;
    }

    cout << "YES" << endl;
    cout << 2 << endl;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cout << (i + j) % n + 1 << " \n"[j == n - 1];
        }
    }
}

E 出题需要魔法

知识点:单调栈、补集计数

考虑固定位置 。在一个包含 的子区间 中, 可见当且仅当:

  • 左侧 中没有比 大的数;
  • 或右侧 中没有比 大的数。

反过来, 不可见,当且仅当左右两边都存在比 大的数。

设:

  • 表示 左边最近的、满足 的位置,若不存在则为
  • 表示 右边最近的、满足 的位置,若不存在则为

包含位置 的子区间总数为:

不可见,则区间必须满足 ​,这样的区间数量为:

因此答案为:

​ 可以用单调栈分别从左到右、从右到左求出。

时间复杂度

void solve() {
    int n;
    cin >> n;
    vector<int> p(n + 1), L(n + 1), R(n + 1);
    for (int i = 1; i <= n; ++i) cin >> p[i];
 
    vector<int> st;
 
    for (int i = 1; i <= n; ++i) {
        while (!st.empty() && p[st.back()] < p[i]) st.pob();
        L[i] = st.empty() ? 0 : st.back();
        st.pb(i);
    }
 
    st.clear();
 
    for (int i = n; i >= 1; --i) {
        while (!st.empty() && p[st.back()] < p[i]) st.pob();
        R[i] = st.empty() ? n + 1 : st.back();
        st.pb(i);
    }
 
    for (int i = 1; i <= n; ++i) cout << i * (n - i + 1) - L[i] * (n - R[i] + 1) << " \n"[i == n];
}

F 出题需要树论

知识点:树形 、前后缀最大值

考虑选择以节点 为根的子树。对于不在该子树内的叶子,它到根的路径和不变;对于在该子树内的叶子,只有从 到该叶子的这一段权值会被异或上 ,而 的父亲到根这一段保持不变。

先以 为根遍历整棵树,求出每个叶子原本的根到叶路径和。一个节点子树内的所有叶子在这个顺序中是连续的一段,记为

再自底向上求:

也就是如果选择 的子树,子树内部到叶子的最大贡献。

对于每个节点

  • 子树外叶子的最大原路径和,可以用叶子路径和的前缀最大值、后缀最大值求出;
  • 子树内叶子的最大新路径和为:

其中 是根到 父亲的原路径和,若 则为

于是选择 的答案为两者最大值,对所有 取最小即可。

时间复杂度

void solve() {
    int n, x;
    cin >> n >> x;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) cin >> a[i];

    vector<vector<int>> g(n + 1);
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        g[u].pb(v), g[v].pb(u);
    }

    vector<int> pa(n + 1), order, pre(n + 1), st;
    st.pb(1);
    pre[1] = a[1];

    while (st.size()) {
        auto u = st.back();
        st.pob();
        order.pb(u);
        for (auto v : g[u]) {
            if (v == pa[u]) continue;
            pa[v] = u;
            pre[v] = pre[u] + a[v];
            st.pb(v);
        }
    }

    vector<bool> leaf(n + 1);
    for (int u = 1; u <= n; ++u) leaf[u] = (n == 1 || (u != 1 && g[u].size() == 1));

    vector<int> id(n + 1, -1), val;
    for (auto u : order) {
        if (leaf[u]) {
            id[u] = val.size() + 1;
            val.pb(pre[u]);
        }
    }

    vector<int> down(n + 1), L(n + 1), R(n + 1);

    for (int i = order.size() - 1; i >= 0; --i) {
        int u = order[i];
        if (leaf[u]) {
            L[u] = R[u] = id[u];
            down[u] = (a[u] ^ x);
        } else {
            int l = n, r = -1;
            int mx = -INF;
            for (int v : g[u]) {
                if (v == pa[u]) continue;
                l = min(l, L[v]);
                r = max(r, R[v]);
                mx = max(mx, down[v]);
            }
            L[u] = l;
            R[u] = r;
            down[u] = (a[u] ^ x) + mx;
        }
    }

    int m = val.size();
    vector<int> premx(m + 1, -INF), sufmx(m + 2, -INF);
    for (int i = 1; i <= m; ++i) premx[i] = premx[i] = max(premx[i - 1], val[i - 1]);
    for (int i = m; i >= 1; --i) sufmx[i] = max(sufmx[i + 1], val[i - 1]);

    int ans = -INF;
    for (int u = 1; u <= n; ++u) {
        int cur = max(max(premx[L[u] - 1], sufmx[R[u] + 1]), (u == 1 ? 0 : pre[pa[u]]) + down[u]);
        ans = (u == 1 ? cur : min(ans, cur));
    }

    cout << ans << endl;
}