AtCoder Beginner Contest 013

A

Problem

给一个大写字母,询问其是从 \(A\) 起的第几个数(\(A\) 是第 \(1\) 个)

Solutions

char c; std::cin >> c;
std::cout << int( c - 'A' + 1) << "\n";

B

Problem

给两个数字 \(a, b\ (1 \leq a, b \leq 9)\)
操作有两种:

  • \(a = (a + 1) \bmod 10\)
  • \(a = (a - 1) \bmod 10\)
    询问 \(a\)\(b\) 的最少操作。

Solutions

显然是两个方向的最短路径的最小值。

\(min((b - a + 10) \bmod 10, (a - b + 10) \bmod 10)\)

C

Problem

一开始你的饱腹度是 \(H\) ,你要度过 \(N\) 天。每天你可以选择三种操作之一:

  • 支出 \(A\) 元,饱腹度增加 \(B\)
  • 支出 \(C\) 元,饱腹度增加 \(D\)
  • 不支出,饱腹度减少 \(E\)
    询问在 \(N\) 天内不饿死(饱腹度 \(\leq 0\) )的情况下,最少支出是多少。因为你有一个神奇的胃,饱腹度没有上限。

\(1 \leq N \leq 5 \times 10^{5}, 1 \leq A,B,D,E \leq 10^{6}, 1 \leq H \leq 10^{9}\)

Solutions

由于饱腹度没有上限,所以操作可以不在乎顺序,只在乎结果。或者可以贪心地先把饱腹度提到最高。

设三种操作次数为 \(X, Y, Z\) 。不难想到 \(O(N^{3})\) 枚举三操作。检查不饿死的最小支出。

由于 \(X + Y + Z = N\) ,于是可以 \(O(N^{2})\) 只枚举 \(X, Y\)\(Z = N - X - Y \geq 0\) 。检查不饿死的最小支出。

注意 \(XB + YD - (N - X - Y)E + H > 0 \quad s.t. \ X,Y \in \mathbb{Z} \wedge X,Y \geq 0 \wedge X + Y <= N\) 。只存在 \(X, Y\) 两个变量,由于是一次函数容易变量分离,然后分配一个到式子两侧:

\[\begin{aligned} &H - NE + X(B + E) + Y(D + E) > 0 \quad s.t. \ X,Y \in \mathbb{Z} \wedge X,Y \geq 0 \wedge X + Y <= N \\ &X \geq \frac{1 + NE - H - Y(D + E)}{B + E} \quad s.t. \ X,Y \in \mathbb{Z} \wedge X,Y \geq 0 \wedge X + Y <= N \\ &X \geq \left \lceil \frac{1 + NE - H - Y(D + E)}{B + E} \right \rceil \quad s.t. \ X,Y \in \mathbb{Z} \wedge X,Y \geq 0 \wedge X + Y <= N \\ &X \geq max\left (0, \left \lceil \frac{1 + NE - H - Y(D + E)}{B + E} \right \rceil \right) \quad s.t. \ X,Y \in \mathbb{Z} \wedge X,Y \geq 0 \wedge X + Y <= N \\ \end{aligned} \]

于是只需要 \(O(N)\) 枚举 \(Y\) ,此次只需要找到最小的 \(X\) ,然后检查是否所有约束被满足。

view
    i64 N, H, A, B, C, D, E;
    std::cin >> N >> H >> A >> B >> C >> D >> E;
    auto divceil = [&] (i64 a, i64 b) -> i64 {
        if (a % b == 0) return a / b;
        else if ((a ^ b) >= 0) return a / b + 1;
        else return a / b;
    };
    const i64 INF = 1LL << 62;
    i64 ans = INF;
    for (int Y = 0; Y <= N; Y++) {
        i64 X = std::max(0LL, divceil(1 + N * E - H - Y * (D + E), B + E));
        if (X >= 0 && Y >= 0 && X + Y <= N) {
            ans = std::min(ans, X * A + Y * C);
        }
    }
    std::cout << ans << "\n";

D

Problem

emm,就是给一块板。

\(n\) 条竖线。

然后 \(m\) 条短横线 \(a_1, a_2, \cdots, a_m (1 \leq a_i \leq n - 1)\) 。按从上往下的顺序,\(a_i\) 表示在第 \(a_i\) 和第 \(a_{i + 1}\) 条竖线之间连一条横线。

从板的最上端,任选一条竖线开始往下走,每遇到一条就要沿着这条横线走到另一条竖线上,直到走到板的最下端。

如图是从第 \(4\) 条竖线向下走,最终会到达第 \(3\) 条竖线。

现在的新问题是。

给出 \(n, m\)\(a\) 数组。将 \(D\) 块一样的板自上而下拼接。询问从第 \(1\) 块板上端的第 \(i\ (1 \leq i \leq n)\) 条直线开始,走到第 \(D\) 块板下端会到哪条直线。

Solutions

不妨先考虑 \(D = 1\) 时应该怎么办。\(1, 2, \cdots, n\) 一定对应 \(to[1], to[2], \cdots, to[3]\)

假设我们得到了这个 \(to\) 数组,那么 \(i\) 最终会到达 \(\underbrace{to[ \cdots to[to[i]]]}_{D'times}\)

不妨设多个 \(to\)\(i\) 的复合为:

\[\begin{aligned} &\underbrace{to[ \cdots to[to[i]]]}_{D'times} \\ = &\underbrace{to \circ to \circ \cdots \circ to}_{D'times} * i \\ = &to^{D}_{i} \\ \end{aligned} \]

显然对于每个 \(i\) ,可以 \(\log D\) 倍增处理出答案。

总的时间复杂度是 \(O(n \log D)\)

问题是 \(to[i]\) 数组怎么求?暴力模拟的话是 \(O(n m)\)

不妨先设 \(to^{1}_i = i\) 。首先这是一个排列。

不难发现 \(to_x\) 是可以递推出来的。

to 的递推方法一:

考虑 \(to_x\) 是到当前为止,\(x\) 能到达 \(to_x\)

于是从前往后递推。

先出现 \(a_1\) ,会导致 \(to_j = a_{1}\)\(to_k = a_{2}\)\(j, k\) 交换位置。\(swap(to_j, to_k)\)

再出现 \(a_2\) ,会导致 \(to_j = a_{2}\)\(to_k = a_{3}\)\(j, k\) 交换位置。\(swap(to_j, to_k)\)

……

一直到 \(a_m\)

如何找到上述的 \(k, k\) ?可以构造映射 \(rec\) 使 \(to[rec[a_i]] = a_i\)

j, k = rec[a[i]], rec[a[i + 1]]
swap(to[j], to[k])
rec[a[i]], rec[a[i + 1]] = j, k

这样就解决了。

to 的递推方法二:

考虑 \(to_x\) 为,\(x\) 最终可以到达 \(to_x\)

于是从后往前递推。

枚举 \(i = m \to 1\)\(swap(to_{a_i}, to_{a_{i + 1}})\) 就做完了。

于是在最终可以通过两种方法之一以 \(O(m)\) 得到 \(to\) 数组,通过 \(O(\log D)\) 得到 \(to^{D}_i\) 。总时间复杂度为 \(O(n \log D)\)

此问题的另一个解法

不妨依旧递推出 \(to\) 数组,使用更简便的递推方法二。

显然 \(\forall i \to to_i\) 是一个置换。为了方便表示不妨定义 \(p = to\)

设置换

\[\begin{aligned} f = \left ( \begin{matrix} 1 & 2 & 3 & \cdots & n - 1 & n \\ p_1 & p_2 & p_3 & \cdots & p_{n - 1} & p_n \\ \end{matrix} \right ) \end{aligned} \]

定义 \(v = [1, 2, \cdots, n]\) ,那么最终只需要求解新排列

\[\underbrace{f \circ f \circ \cdots \circ f}_{D'times} * v = f^{D} * v \]

暴力的单次置换乘法时间复杂度 \(O(n)\) ,可以通过快速幂 \(O(\log D)\) 计算 \(f^{D}\) 。这时候已经可以 \(O(m + n \log D)\) 解决问题。

实际上一个置换 \(f\) 可以 \(O(n)\) 分解为若干个循环置换 \(g_i\)

\[\begin{aligned} f = g_1 \circ g_2 \circ \cdots \circ g_k \\ f^{D} = g_1^{D} \circ g_2^{D} \circ \cdots \circ g_k^{D} \\ \end{aligned} \]

循环置换的幂次 \(g_{i}^{D} = g_{i}^{D - 1} \circ g\) 的结果是 \(g\)上部分向量循环左移位 \(D \bmod |g_i|\) 次,可以 \(O(|g_i|)\) 做到。于是 \(\sum g_i^{D}\) 的时间复杂度为 \(O(\sum |g_i|) = O(n)\)

这时候 \(O(n)\) 计算置换 \(\forall g_i^{D}\) 的乘积,得到 \(f^{D}\)

比如:

\(a_i = i\)

\[\begin{aligned} f &= \left ( \begin{matrix} a_1 & a_2 & a_3 & a_{4} & a_5 \\ a_{4} & a_{2} & a_{5} & a_{3} & a_{1} \\ \end{matrix} \right ) = \left ( \begin{matrix} a_1 & a_4 & a_3 & a_{5} \\ a_{4} & a_{3} & a_{5} & a_{1} \\ \end{matrix} \right ) \circ \left ( \begin{matrix} a_2 \\ a_2 \\ \end{matrix} \right ) \\ f^{2} &= \left ( \begin{matrix} a_1 & a_2 & a_3 & a_{4} & a_5 \\ a_{3} & a_{2} & a_{1} & a_{5} & a_{4} \\ \end{matrix} \right ) = \left ( \begin{matrix} a_1 & a_4 & a_3 & a_{5} \\ a_{3} & a_{5} & a_{1} & a_{4} \\ \end{matrix} \right ) \circ \left ( \begin{matrix} a_2 \\ a_2 \\ \end{matrix} \right ) \\ f^{3} &= \left ( \begin{matrix} a_1 & a_2 & a_3 & a_{4} & a_5 \\ a_{5} & a_{2} & a_{4} & a_{1} & a_{3} \\ \end{matrix} \right ) = \left ( \begin{matrix} a_1 & a_4 & a_3 & a_{5} \\ a_{5} & a_{1} & a_{4} & a_{3} \\ \end{matrix} \right ) \circ \left ( \begin{matrix} a_2 \\ a_2 \\ \end{matrix} \right ) \\ \end{aligned} \]

很直观地,\(f^{D}\)\(a_i\) 的下一位被\(g_1^{D} \circ g_2^{D} \circ \cdots \circ g_k^{D}\)\(a_i\) 的下一位计算出。

第一种解法不依赖于置换的性质,倍增时间复杂度 \(O(m + n \log D)\)

第二种解法依赖置换,快速幂时间复杂度 \(O(m + n \log D)\) 。然后分解成循环置换可以 \(O(m + n)\) 实现。

倍增是比较显然的,不想写实现了。

\(O(m + n \log D)\) 的置换快速幂实现:

view
std::vector<int> operator * (std::vector<int> const &p, std::vector<int> const &q) {
    int n = p.size() - 1;
    assert(n == (int)q.size() - 1);
    std::vector<int> res(n + 1);
    for (int i = 1; i <= n; i++) res[i] = q[p[i]];
    return res;
}

std::vector<int> ksm(std::vector<int> p, i64 pw) { // 可能常数会比 pow 好?复杂度乘 log pw
    int n = p.size() - 1;
    std::vector<int> res(n + 1); std::iota(res.begin(), res.end(), 0);
    for (;pw;pw>>=1,p=p*p)if(pw&1)res=res*p; return res;
}

void solve() {
	int N, M, D; std::cin >> N >> M >> D;
	std::vector<int> a(M + 1);
	for (int i = 1; i <= M; i++) std::cin >> a[i];
    std::vector<int> t(N + 1);
    std::iota(t.begin(), t.end(), 0);
	for (int i = M; i >= 1; --i) std::swap(t[a[i]], t[a[i] + 1]);

    std::vector<int> q(N + 1); std::iota(q.begin(), q.end(), 0);
    std::vector<int> ans;
    ans = ksm(t, D) * q;
    for (int i = 1; i <= N; i++) std::cout << ans[i] << "\n";
}

\(O(m + n)\) 的置换分解实现:

view
struct permutation {
    // 只处理下标的排列置换!!!
    // 1 base 常用于置换
    // 1, 2, ..., n -> p_1, p_2, ..., p_n
    std::vector<int> p; int n;
    std::vector<std::vector<int> > cycs; // 循环置换用 0 base 存
    permutation () {}
    permutation (std::vector<int> p_) {
        p = p_;
        n = p_.size() - 1;
        std::vector<int> vis(n + 1, 0);
        for (int i = 1; i <= n; i++) if (!vis[i]) {
            int x = i;
            vis[x]++;
            std::vector<int> cyc{x};
            while (!vis[p[x]]) {
                x = p[x];
                vis[x]++;
                cyc.push_back(x);
            }
            cycs.push_back(cyc);
        }
    }
    std::vector<int> pow(i64 pw) {
        // 1, 2, ..., n -> p_1, p_2, ..., p_n
        assert(pw > 0); // 考虑是否存在幺元
        std::vector<int> q(n + 1);
        for (auto cyc : cycs) {
            int m = cyc.size();
            int g = pw % m;
            // c_0, c_2, ..., c_{m - 1} -> c_g, c_{g + 1}, ..., c_{g - 1}
            for (int i = 0; i < m; i++) {
                q[cyc[i]] = cyc[(i + g) % m];
            }
        }
        return q;
    }

    std::vector<std::vector<int> > getcycs() {
        return cycs;
    }
};

std::vector<int> operator * (std::vector<int> const &p, std::vector<int> const &q) {
    int n = p.size() - 1;
    assert(n == (int)q.size() - 1);
    std::vector<int> res(n + 1);
    for (int i = 1; i <= n; i++) res[i] = q[p[i]];
    return res;
}

void solve() {
	int N, M, D; std::cin >> N >> M >> D;
	std::vector<int> a(M + 1);
	for (int i = 1; i <= M; i++) std::cin >> a[i];
    std::vector<int> t(N + 1);
    std::iota(t.begin(), t.end(), 0);
	for (int i = M; i >= 1; --i) std::swap(t[a[i]], t[a[i] + 1]);
    permutation p(t);

    std::vector<int> q(N + 1); std::iota(q.begin(), q.end(), 0);
    std::vector<int> ans;
    ans = p.pow(D) * q;
    for (int i = 1; i <= N; i++) std::cout << ans[i] << "\n";
}
posted @ 2024-09-12 09:36  03Goose  阅读(37)  评论(0)    收藏  举报