题解 | 牛客周赛 Round 144

A 我是谁?

知识点:字符串、计数。

只需要统计 $\texttt{A,B,C,D}$ 四种字符出现次数,判断是否四个计数完全相同(即最大值等于最小值)即可。

时间复杂度 $\mathcal{O}(n)$​。

void solve() {
    int n;
    string s;
    cin >> n >> s;
    vector<int> a(4);
    for (auto v : s) a[v - 'A']++;
    cout << (max(all(a)) == min(all(a)) ? "Yes" : "No") << endl;
}

B 我是清楚姐姐

法 $\texttt{1}$:

知识点:构造、贪心、最大公约数。

考虑让第 $i$ 列的最大公约数为 $i$。最简单的办法是先把第一行放成 $1,2,\dots,n$,这样第 $i$ 列已经包含了 $i$,只要之后放进第 $i$ 列的数都能被 $i$ 整除,那么该列的 $\gcd$ 就一定恰好为 $i$。

于是问题转化为:把 $n+1\sim n^2$ 这些数填到若干列中,数字 $x$ 只能填入满足 $i\mid x$ 的第 $i$ 列,且每列最终有 $n$ 个数。

从大到小枚举 $x$,每次优先放入当前还能容纳它的最大编号列。编号越大的列可用倍数越少,更应该优先满足;若当前数字找不到任何还有空位的因子列,则无法完成构造,输出 $-1$。否则所有列被填满后,列 gcd 分别为 $1,2,\dots,n$,满足要求。

时间复杂度 $\mathcal{O}(n^3)$​。

void solve() {
    int n;
    cin >> n;
    vector<vector<int>> a(n, vector<int>(n));
    for (int i = 0; i < n; ++i) a[0][i] = i + 1;
    int now = n * n;
    vector<int> cnt(n, 1);
    while (now > n) {
        bool f = 1;
        for (int i = n; i >= 1; --i)
            if (__gcd(now, i) == i && cnt[i - 1] != n) {
                a[cnt[i - 1]][i - 1] = now, now--, cnt[i - 1]++;
                f = 0;
                break;
            }
        if (f) {
            cout << -1 << endl;
            return;
        }
    }
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j) cout << a[i][j] << " \n"[j == n - 1];
}

法 $\texttt{2}$:

知识点:打表

不难发现只有当 $n=1$ 、 $n=2$ 或 $n=3$​ 才有解。

如果你需要一个证明的话:

打表发现 $n=1,2,3$ 时有解,接下来证明当 $n\geq 4$ 时无解:

  • $n=2k,\ k\in Z$ :$\gcd$ 为偶数的列共有 $m$ 个:$2,4,…,n$ 。这些列里的数全都必须是偶数,所以它们一共要用掉 $m \cdot n = n^2/2$​ 个偶数,也就是说,所有偶数都已经被这些列用光了。 但 $\gcd$ 为 $n-1$ 的那一列,$n-1$ 是奇数。在 $1\sim n^2$ 中,$n-1$ 的倍数一共有
    $$
    \left\lfloor \frac{n^2}{n-1}\right\rfloor = n+1
    $$
    个,其中奇数倍最多只有
    $$
    \left\lceil \frac{n+1}{2}\right\rceil = \frac n2+1
    $$
    个,所以这一列至少要用
    $$
    n-\left(\frac n2+1\right)=\frac n2-1>0
    $$
    个偶数,矛盾。
  • $n=2k+1,\ k\in Z$:$\gcd$ 为偶数的列有 $m$ 列:$2,4,\dots,n-1$ 。这 $m$ 列全都只能放偶数,所以一共用掉 $m\cdot n = \frac{n(n-1)}2$ 个偶数。 总偶数个数是
    $$
    \frac{n^2-1}{2}=\frac{n(n-1)}2+\frac{n-1}{2}
    $$
    所以还剩 $\frac{n-1}{2}$ 个偶数。

​ 而 $\gcd$ 为 $n$ 的那一列,因为 $n$ 是奇数,它的 $n$ 个倍数里,偶数倍正好有 $\frac{n-1}{2}$ 个,所以它会把这剩下的偶数全部用完。但 $n\ge 5$ 时,$n-2$ 这列也存在,而且 $n-2$ 也是奇数。 $n-2$ 的倍数总数至多为
$$
\left\lfloor \frac{n^2}{n-2}\right\rfloor \le n+3
$$
​ 所以奇数倍最多只有 $\frac{n+3}{2}<n$ 个,不够填满一整列,因此它至少也需要一个偶数。可现在偶数已经被用光了,矛盾。

所以 $n\geq 4$ 都无解。

时间复杂度 $\mathcal{O}(1)$​。

void solve() {
    int n;
    cin >> n;
    if (n == 1) cout << 1 << endl;
    else if (n == 2) cout << "1 2\n3 4" << endl;
    else if (n == 3) cout << "1 2 3\n5 4 6\n7 8 9" << endl;
    else cout << -1 << endl;
}

C 其实我是小苯

法 $\texttt{1}$ :

知识点:数学、分类讨论、大整数输出。

$n$ 位正整数的取值范围是 $[10^{n-1},10^n-1]$。不妨令 $n\ge m$。

最小值看两个区间是否相交:

  • 若 $n=m$,可以取 $a=b$,最小值为 $0$。
  • 若 $n=m+1$,最小值为 $10^m-(10^m-1)=1$。
  • 否则最小值为 $10^{n-1}-(10^m-1)$,按十进制直接输出即可。

最大值一定由较大的 $n$ 位最大数和较小的 $m$ 位最小数相减得到,即
$$
(10^n-1)-10^{m-1}.
$$
时间复杂度 $\mathcal{O}(n+m)$。

void solve() {
    int n, m;
    cin >> n >> m;
    if (n < m) swap(n, m);
    if (n == m) {
        cout << 0;
    } else if (n - 1 == m) {
        cout << 1;
    } else {
        for (int i = m + 1; i < n; ++i) cout << 9;
        for (int i = 1; i < m; ++i) cout << 0;
        cout << 1;
    }
    cout << " ";
    for (int i = m; i < n; ++i) cout << 9;
    cout << 8;
    for (int i = 1; i < m; ++i) cout << 9;
    cout << endl;
}

法 $\texttt{2}$ :

知识点:$\texttt{Python}$ 。

$\texttt{Python}$​ 做这种题目就是很强,推导过程同上。

时间复杂度 $\mathcal{O}(1)$。

n, m = map(int, input().split())
if n < m:
    n, m = m, n
if n == m:
    print(0, pow(10, n) - 1 - pow(10, m - 1))
else:
    print(pow(10, n - 1) - pow(10, m) + 1, pow(10, n) - 1 - pow(10, m - 1))

D 骗你的,其实我是小红

知识点:位运算、同余计数、组合计数。

因为 $k$ 是 $2$ 的非负整数次幂,设 $k=2^t$。一个数能被 $k$ 整除,等价于低 $t$ 位全为 $0$。因此
$$
i\oplus j
$$
是 $k$ 的倍数,当且仅当 $i,j$ 的低 $t$ 位完全相同,也就是 $i\equiv j\pmod k$。

所以只需要统计区间 $[l,r]$ 中每个模 $k$ 余数出现了多少次。设区间长度为 $n=r-l+1$,每个余数出现次数只可能是 $\lfloor n/k\rfloor$ 或 $\lfloor n/k\rfloor+1$。令 $b=\lfloor n/k\rfloor,e=n\bmod k$,答案为
$$
e\binom{b+1}{2}+(k-e)\binom{b}{2}.
$$

时间复杂度 $\mathcal{O}(1)$​。

void solve() {
    int l, r, k;
    cin >> l >> r >> k;
    int n = r - l + 1, b = n / k, e = n % k;
    auto S = [&](int x) { return x * (x - 1) / 2; };
    cout << e * S(b + 1) + (k - e) * S(b) << endl;
}

E 好吧,我是Bingbong

知识点:树、$\texttt{DFS}$、前缀贡献。

注水过程本质上是一个由管道高度决定的 $\texttt{DFS}$ 顺序:对于同一个瓶子的所有子树,管道位置越低,越先被完全灌满。一个节点 $u$ 的瓶子达到满水时,$u$ 的整棵子树也已经全部注满。

先计算
$$
siz_u=\sum_{x\in subtree(u)}h_x,
$$
表示子树 $u$ 全部灌满需要的水量。

再设 $st_u$ 表示开始灌入子树 $u$ 之前,系统中已经存在的水量。对节点 $u$,按管道高度从小到大枚举儿子 $v$。若当前儿子管道高度为 $w_v$,且此前已经灌满的较低儿子子树总量为 pre,则
$$
st_v=st_u+w_v+pre.
$$
于是节点 $v$ 自己达到满水时,总水量为
$$
ans_v=st_v+siz_v.
$$
根节点没有外部前缀,$st_1=0$,答案为整棵树容量和。

时间复杂度 $\mathcal{O}(n\log n)$。

void solve() {
    int n;
    cin >> n;
    vector<int> h(n + 1), siz(n + 1), st(n + 1), ans(n + 1);
    for (int i = 1; i <= n; ++i) cin >> h[i];
    vector<int> fa(n + 1, 0);
    for (int i = 2; i <= n; ++i) cin >> fa[i];
    vector<int> w(n + 1, 0);
    for (int i = 2; i <= n; ++i) cin >> w[i];
    vector<vector<PII>> g(n + 1);
    for (int i = 2; i <= n; ++i) g[fa[i]].push_back({w[i], i});
    for (int i = 1; i <= n; ++i) sort(all(g[i]));
    for (int i = 1; i <= n; ++i) siz[i] = h[i];
    for (int i = n; i >= 2; --i) siz[fa[i]] += siz[i];
    st[1] = 0, ans[1] = siz[1];
    for (int u = 1; u <= n; ++u) {
        int pre = 0;
        for (auto [ww, v] : g[u]) st[v] = st[u] + ww + pre, ans[v] = st[v] + siz[v], pre += siz[v];
    }
    for (int i = 1; i <= n; ++i) cout << ans[i] << " \n"[i == n];
}

F 牛魔!

法 $\texttt{1}$:

知识点:树上贪心、异或操作、构造性调整。

把根的深度设为 $0$。若选择深度至少为 $2$ 的节点 $u$ 作为操作中的孩子节点,则一次操作会同时翻转 $u,fa_u,fa_{fa_u}$。从叶子往根处理时,处理到某个深度 $\ge 2$ 的节点 $u$ 后,之后不会再影响它的子树,因此若 $a_u=0$,就直接执行这次操作把它变成 $1$。这样可以保证所有深度至少为 $2$ 的节点最终全为 $1$,祖先受到的影响留到之后再处理。

这一步结束后,只有根和根的儿子可能不是 $1$。如果根已经是 $1$,再为了调整根儿子而翻转某条链,至少会让一个已经为 $1$ 的深层节点变成 $0$,不会变优。

只有一种情况还能多赚 $1$:根为 $0$,某个根儿子也为 $0$,并且该儿子所在子树中存在一个深度 $\equiv 2\pmod 3$ 的节点。沿这条根到该节点的链组合若干次操作,可以做到只翻转根、这个根儿子和该深层节点。这样两个 $0$ 变成 $1$,一个深层 $1$ 变成 $0$,总和增加 $1$。

因此先用自底向上的贪心统计当前答案,再判断上述额外调整是否存在即可。

时间复杂度 $\mathcal{O}(n)$​。

void solve() {
    int n;
    cin >> n;
    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].push_back(v), g[v].push_back(u);
    }
    vector<int> fa(n + 1), dep(n + 1), bel(n + 1), ord, st;
    st.push_back(1);
    fa[1] = 0;
    while (st.size()) {
        int u = st.back();
        st.pop_back();
        ord.push_back(u);
        for (auto v : g[u]) {
            if (v == fa[u]) continue;
            fa[v] = u, dep[v] = dep[u] + 1, bel[v] = (u == 1 ? v : bel[u]);
            st.push_back(v);
        }
    }
    vector<int> ok(n + 1, 0);
    for (int i = n - 1; i >= 0; --i) {
        int u = ord[i];
        if (dep[u] < 2) continue;
        if (dep[u] % 3 == 2) ok[bel[u]] = 1;
        if (a[u] == 0) a[u] ^= 1, a[fa[u]] ^= 1, a[fa[fa[u]]] ^= 1;
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) ans += a[i];
    if (a[1] == 0) {
        for (auto v : g[1]) {
            if (fa[v] == 1 && a[v] == 0 && ok[v]) {
                ++ans;
                break;
            }
        }
    }
    cout << ans << endl;
}

法 $\texttt{2}$:

知识点:树、操作等价性、子树 $\texttt{DP}$​

一次操作会同时翻转 $fa(i)、i、son(i)$ 三个点,把树按深度 $\mod 3$ 分层后,这三个点一定分别落在三层里,所以操作本质上只是在改三层里 $1$ 的奇偶性。

于是对每个子树,只需要关心三层分别有多少个点,以及三层 $1$ 的奇偶关系。

设三层奇偶为 $P0,P1,P2$ ,那么 $P1\oplus P2$ 是子树内部不变量,$P0\oplus P2$ 只和这个子树向外接出去的状态有关,所以每个根儿子子树最后只需要算出一个二进制状态 $q = P0\oplus P2$ ,以及在这个状态下最多能放多少个 $1$ 。

对子树里某一层:

  • 如果这一层有 $cnt$ 个点,要求最终 $1$ 的奇偶性为 $p$ ,那这一层最多能取 $cnt$ 个 $1$ 。
  • 若奇偶不对就少留一个变成 $cnt-1$ 。
  • 如果 $cnt=0$ 还要求奇数那就是非法。

所以对每个根儿子子树 $\texttt{DFS}$ 一遍,统计三层点数 $cnt[0/1/2]$ ,再枚举 $P2$ ,由 $P1 = P2\oplus Ic$ 推出 $P1$ ,由 $P0 = q \oplus P2$ 推出 $P0$,就能得到这棵子树在状态 $q$ 下的最优贡献 $M[q]$ 。不同儿子子树之间只需要按异或合并这些 $q$ ,设 $dp[s]$ 表示处理完若干棵子树后,它们的 $q$ 异或和为 $s$ 时最多能得到多少个 $1$,那么有
$$
dp[s\oplus q] = max(dp[s\oplus q], dp[s] + M[q])
$$
最后根节点自身也由全局异或状态决定,直接把它补上后取最大值就是答案。

时间复杂度是 $\mathcal{O}(n)$ 。

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    vector<vector<int>> g(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        g[u].pb(v), g[v].pb(u);
    }
    int I = a[1], dp[2] = {0, -INF};
    auto get = [](int count, int req) {
        if (count == 0) return req == 0 ? 0 : -INF;
        return count - (count % 2 != req);
    };
    for (auto fc : g[1]) {
        int cnt[3] = {0, 0, 0}, Ic = 0;
        auto dfs = [&](auto &self, int u, int p, int d) -> void {
            cnt[d % 3]++;
            if (d % 3 != 1) I ^= a[u];
            if (d % 3 != 0) Ic ^= a[u];
            for (int v : g[u])
                if (v != p) self(self, v, u, d + 1);
        };
        dfs(dfs, fc, 1, 1);
        int M[2] = {-INF, -INF};
        for (int P2 = 0; P2 <= 1; P2++) {
            int P1 = P2 ^ Ic;
            for (int P0 = 0; P0 <= 1; P0++) {
                int q = P0 ^ P2, ones = get(cnt[0], P0) + get(cnt[1], P1) + get(cnt[2], P2);
                M[q] = max(M[q], ones);
            }
        }
        int ndp[2] = {-INF, -INF};
        for (int S = 0; S <= 1; S++)
            for (int q = 0; q <= 1; q++) ndp[S ^ q] = max(ndp[S ^ q], dp[S] + M[q]);
        dp[0] = ndp[0], dp[1] = ndp[1];
    }

    int ans = max(dp[0] + (I ^ 0), dp[1] + (I ^ 1));
    cout << ans << endl;
}

备注

#define int long long
constexpr int INF = 1e9 + 7;
int __INIT__ = []() {
    ios::sync_with_stdio(0), cin.tie(0);
    cout << fixed << setprecision(15);
    return 0;
}();
暂无评论

发送评论 编辑评论


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