AtCoder Beginner Contest 044

A
题意:

需要住连续 \(n\) 天。收费标准是前 \(k\) 天每晚 \(x\) 元,之后每晚 \(y\) 元。总价为?

题解:
\(min(k, n)x + max(n - k, 0)y\)

B
题意:
给一个字符串 \(s\) ,如果每个小写字符出现次数为偶数次,则是美丽的,否则则否。询问 \(s\) 是否美丽。

\(1 \leq |s| \leq 100\)

题解:
注意题目要求的大小写输出,没有 spj 。

std::string s; std::cin >> s;
int v[26] = {0};
for (int i = 0; i < (int)s.size(); i++) if (s[i] >= 'a' && s[i] <= 'z') {
    v[s[i] - 'a'] ^= 1;
}
int ok = 1;
for (int i = 0; i < 26; i++) ok &= v[i] == 0;
std::cout << (ok ? "Yes" : "No") << "\n";

C
题意:
给定 \(n\) 个数字 \(a_1 a_2 \cdots a_n\) ,给定一个 \(m\) ,有多少种选择数字(至少选择一个)的方案,使得它们的平均值为 \(m\) ?取出顺序不同看作一个方案。

\(1 \leq n, m, a_i \leq 50\)

题解:

考虑 dfs 大概只能搜 \(n \leq 20\) 的情况。

实际考虑答案怎么算,一定是枚举所有数字里选了 \(x\) 个数,他们的平均值是 \(m\) 的方案是多少。

那么就是枚举所有数字里选了选了 \(x\) 个数,他们的和为 \(mx\) 的方案是多少。

这是不是一个经典的模型?“所有元素里选 \(x\) 个”。

是不是就是考虑了 \(n\) 个数,要选 \(x\) 个。

是不是就是考虑了 \(i\) 个数选 \(j\) 个的方案,是不是就是 DP 大概率能够解决的问题?

于是 DP :考虑了前 \(i\) 位,选择了 \(j\) 个数字,和为 \(k\) 的方案数。实现起来是一个背包。

良序下 \(f(0, 0, 0) = 1\)

时间复杂度为 \(O(n^{2} V)\)\(V= \sum a_i\)\(T = 50^{4} = 6250000 = 6.25 \times 10^{6}\)

突然发现啥玩意空间优化都忘了,来分析裸 DP 。

\[f(i, j, k) \leftarrow \begin{cases} f(i - 1, j, k) \ s.t.\ j \leq i \\ f(i - 1, j - 1, k - a_i) \ s.t.\ j \leq i \\ \end{cases} \]

当优化 \(i\) 维后,\(i - 1\)\(i\) 会被滚到一维,只需要考虑 \(j\) 维受到的影响。

此时的 \(j\) 维要用倒序 DP 。约束是 \(j \leq i\)

直觉上总数不会很多,而且题目也没要取模,应该是答案不大。实测要开 long long 。

ll f[55][2505];
void solve() {
    int n, A; std::cin >> n >> A;
    std::vector<int> a(n + 1);
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        sum += a[i];
    }
    f[0][0] = 1;
    for (int i = 0; i <= n; i++) {
        for (int j = i; j >= 0; --j) {
            for (int k = 0; k <= sum; k++) {
                if (j == 0 && k == 0) continue;
                if (j >= 1 && k >= a[i]) f[j][k] += f[j - 1][k - a[i]];
            }
        }
    }
    ll ans = 0;
    for (int j = 1; j <= n; j++) {
        for (int k = 1; k <= sum; k++) if (k == j * A) {
            ans += f[j][k];
        }
    }
    std::cout << ans << "\n";
}

D
题意:

对任意正整数 \(b(b \geq 2)\)\(n(n \geq 1)\) ,定义 \(f(b, n)\) 为:

\[\begin{cases} n,\ s.t.\ n < b \\ f(b, \lfloor \frac{n}{b} \rfloor) + n \bmod b\ s.t.\ n \geq b \end{cases} \]

给一个正整数 \(n\)\(s\) ,确定是否存在一个 \(b(b \geq 2)\) 使得 \(f(b, n) = s\) 。如果存在,找到最小的 \(b\)

\(1 \leq n, s \leq 10^{11}\)

题解:

这个东西就是说 \(f(b, n) = \sum a_i\) ,且 \(n = \sum a_i x_i\) 。然后会给出 \(f(b, n) = s\) 。为了方便描述,把 \(b\) 换成 \(x\)

首先 \(x \geq 2\) 是没有争议的,一维多项式的变量最小为 \(2\) ,这是是我们熟悉的二进制表示。否则不存在这种多项式。

首先若 \(n < s\) ,不存在多项式满足 \(\sum a_i x_i < \sum a_i\)
\(n = s\) 时只需 \(x > s\) ,任意多项式 \(s x^{0} = n\)
然后是 \(n > s\) ,若有解,则 \(x \in [2, n]\) 。直接枚举会产生至少 \(O(n)\) 的开销,还要算上 check 的复杂度。

直觉上,如果可以调整一个多项式的变量 \(x\) ,会产生一种根号算法。

  1. \(x \in [2, \sqrt{n} - 1]\)
    暴力枚举,枚举复杂度 \(O(\sqrt{n})\) ,加上 check 复杂度 \(\sum_{i = 2}^{\sqrt{n}} \log_i n\) 不影响瓶颈。

  2. \(x = \sqrt{n}\)
    若存在 \(x = \sqrt{n}\) ,则 \(0 \times x^{0} + 0 \times x^{1} + 1 \times x^{2} = n\) 。不妨也一并与 \(1\) 枚举计算。

  3. \(x \in [\sqrt{n} + 1, s]\)
    则多项式为 \(a_0 x^0 + a_1 x^{1} = n\) ,此时方程会有两个等式和多个不等式约束
    \(M \geq \sqrt{n} + 1\) ,通常为保证精度会多加一位。比如 M = sqrt(n) + 2

    \[\begin{cases} a_0 + a_1 = s \\ a_0 + a_1 x = n \\ M \leq x \leq n \\ 0 \leq a_0 < x \\ 1 \leq a_1 < x \\ \end{cases} \]

    此时只需要让 \(x\) 用其他数表示,即提出到一边。
    首先 \(a_0, a_1\) 两个未知数可以通过 \(s\) 变成一个未知数。即 \(a_0 = s - a_1 \in [1, M - 1]\)\(a_0\) 的约束被更新,缩小枚举域(这时候已经够了)。
    往下代,于是有 \(s - a_1 + a_1 x = n \Rightarrow a_1(x - 1) = n - s\)\(a_1\) 的约束更新为 \([max(1, \lceil \frac{n - s}{n - 1} \rceil ), min(M - 1, \lfloor \frac{n - s}{M - 1} \rfloor]\)
    于是分离出了 \(x\) ,即 \(x = \frac{n - s}{a_1} + 1\) 。于是只需枚举 \(a_1\) 检查 \(\frac{n - s}{a_1}\) 是否有解。
    逼紧 \(a_1\) 的约束只是为了减小枚举范围,甚至可以不完全逼紧。
    而只依靠一个 \(a_1\) 放缩后的约束判断会存在误差,只有所有约束同时判断是正确的。

    ll n, s; std::cin >> n >> s;
    ll ans = inf;
    if (n < s) ans = -1;
    else if (n == s) ans = s + 1;
    else {
        ll M = std::sqrt<ll>(n) + 2;
        for (ll i = 2; i < M; i++) {
            // f(i, n) = s
            ll x = n, sum = 0;
            while (x) {
                sum += x % i;
                x /= i;
            }
            if (sum == s)
                ans = std::min(ans, i);
        }
        for (ll i = std::max(1LL, (n - s + n - 2) / (n - 1)); i <= std::min(M - 1, (n - s) / (M - 1)); i++) if ((n - s) % i == 0) {
            ll x = (n - s) / i + 1;
            if (std::max({M - 1, s - i, i}) < x && x <= n && 0 <= s - i && std::max(s - i, i) < x)
                ans = std::min(ans, x);
        }
        if (ans == inf) ans = -1;
    }
    std::cout << ans << "\n";

实际实现中时间太卡可以扣扣常数,\(< M\) 时正序枚举 \(x\)\(\geq M\) 时逆序枚举 \(a_1\) ,及时 \(break\)

posted @ 2023-03-01 19:56  03Goose  阅读(28)  评论(0)    收藏  举报