跳转至

ABC333(A-F)

A - Three Threes

题目大意

给你一个整数 \(N(1 \le N \le 9)\),将 \(N\) 重复打印 \(N\) 次。

参考代码
#include <iostream>

using namespace std;

int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        cout << n;
    return 0;
}
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        for (int i = 0; i < n; i++)
            System.out.print(n);
        in.close();
    }
}

B - Pentagon

题目大意

如图所示是一个正五边形,图中有 \(5\) 个点,以这些点为端点构造两条线,问这两条线的长度是否相等。

解题思路

根据对称性可以对线的端点进行同时移动,将两条线的一端都移动到点 \(A\),然后判断另一端是否相同或者对称即可。

参考代码
#include <iostream>
#include <cstdio>

using namespace std;

int main()
{
    char s[5];
    int line[2][2];
    for (int i = 0; i < 2; i++)
    {
        cin >> s;
        line[i][0] = s[0] - 'A';
        line[i][1] = s[1] - 'A';
        while (line[i][0])
        {
            line[i][0] = (line[i][0] + 1) % 5;
            line[i][1] = (line[i][1] + 1) % 5;
        }
    }
    if (line[0][1] == line[1][1] || line[0][1] + line[1][1] == 5)
        puts("Yes");
    else
        puts("No");
    return 0;
}
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int[][] line = new int[2][2];
        for (int i = 0; i < 2; i++) {
            String s = in.next();
            line[i][0] = s.charAt(0) - 'A';
            line[i][1] = s.charAt(1) - 'A';
            while (line[i][0] > 0) {
                line[i][0] = (line[i][0] + 1) % 5;
                line[i][1] = (line[i][1] + 1) % 5;
            }
        }
        if (line[0][1] == line[1][1] || line[0][1] + line[1][1] == 5)
            System.out.println("Yes");
        else
            System.out.println("No");
        in.close();
    }
}

C - Repunit Trio

题目大意

我们称 \(1, 11, 111, 1111, \cdots\)repunit数列,选择三个 repunit 数列的数(允许重复选择),将三个数字相加得到的数称为 3-repunit 数。给你一个整数 \(N(1 \le N \le 333)\),问,第 \(N\) 小的 3-repunit 数是多少?

解题思路

样例中给出了 \(N = 333\) 的时候答案是 \(112222222233\),这样在 dfs 搜索的时候,每一个数字最大不会超过 \(111111111111\)\(12\)\(1\)),枚举量不会超过 \(12 ^3\)。进一步的,给出一种线性时间复杂度的枚举方法,在 dfs 枚举的时候,可以对下一层的数字的上界进行限制,例如:

1       1       1       3
11      1       1       13
11      11      1       23
11      11      11      33
111     1       1       113
111     11      1       123
111     11      11      133
111     111     11      233
111     111     111     333
1111    1       1       1113

按照这种顺序进行枚举,得到的数字一定也是按顺序的。设三个数字为 \(x,y,z\),当 \(x\) 确定后,其 \(x+y+z\) 的上界为 \(3x\),当 \(x\)\(y\) 确定后,有 \(x+y+z\) 的上界为 \(x + 2y\)。而 \(x\) 的扩大是从 \(x\) 变成 \(10x+1\),显然有 \(10x+1 > 3x\),因此,应该首先让 \(x\) 变大,并作为上界对后面的数字进行限制。

这种方式枚举的时间复杂度为 \(O(n)\),并且这个规律还可以进一步扩展,即跳过枚举的过程,直接枚举出第 \(N\) 个数字所需要的 \(x, y, z\)

参考代码
#include <iostream>

using namespace std;

typedef long long LL;
int cnt, n;
void dfs(int cur, LL sum, LL last)
{
    if(cur == 3)
    {
        if(++cnt == n)
            cout << sum << endl;
        return;
    }
    for(LL x = 1; x <= last && cnt < n; x = 10 * x + 1)
        dfs(cur+1, sum+x, x);
}

int main()
{
    cin >> n;
    dfs(0, 0, 111111111111);
    return 0;
}
import java.util.Scanner;

public class Main {
    private static int cnt, n;

    private static void dfs(int cur, long sum, long last) {
        if (cur == 3) {
            if (++cnt == n)
                System.out.println(sum);
            return;
        }
        for (long x = 1; x <= last && cnt < n; x = 10 * x + 1)
            dfs(cur + 1, sum + x, x);
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        dfs(0, 0, 111111111111l);
        in.close();
    }
}

D - Erase Leaves

题目大意

给你一棵 \(N\) 个节点的树,你可以执行一种操作:删除一个树中度数为 \(1\) 的节点以及这个点的连边。问:最少执行多少次操作才能将点 \(1\) 删除?

解题思路

以点 \(1\) 为根节点 dfs 一次,统计每个点的子树大小。最后枚举点 \(1\) 所有的孩子,留下最大的那颗子树,其余的就是需要删除的点。

参考代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int maxn = 3E5 + 10;
const int maxm = 2 * maxn;
int n;
int head[maxn], edge[maxm], ne[maxm], idx = 1;
void add(int u, int v)
{
    edge[idx] = v;
    ne[idx] = head[u];
    head[u] = idx++;
}

int son[maxn];
void dfs(int u, int f)
{
    son[u] = 1;
    for(int i = head[u]; i; i = ne[i])
    {
        int v = edge[i];
        if(v == f)
            continue;
        dfs(v, u);
        son[u] += son[v];
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d %d", &u, &v);
        add(u, v);
        add(v, u);
    }
    dfs(1, 0);
    vector<int> d;
    for(int i = head[1]; i; i = ne[i])
    {
        int v = edge[i];
        d.push_back(son[v]);
    }
    sort(d.begin(), d.end());
    cout << son[1] - d.back();
    return 0;
}
import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    private static int[] head, edge, ne;
    private static int idx = 1;
    private static int[] son;

    private static void graph_init(int n, int m) {
        head = new int[n];
        edge = new int[m];
        ne = new int[m];
    }

    private static void add(int u, int v) {
        edge[idx] = v;
        ne[idx] = head[u];
        head[u] = idx++;
    }

    private static void dfs(int u, int f) {
        son[u] = 1;
        for (int i = head[u]; i > 0; i = ne[i]) {
            int v = edge[i];
            if (v == f)
                continue;
            dfs(v, u);
            son[u] += son[v];
        }
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int n = in.nextInt();
        graph_init(n + 1, 2 * n);
        son = new int[n + 1];

        for (int i = 1; i < n; i++) {
            int u, v;
            u = in.nextInt();
            v = in.nextInt();
            add(u, v);
            add(v, u);
        }
        dfs(1, 0);
        ArrayList<Integer> d = new ArrayList<>();
        for (int i = head[1]; i > 0; i = ne[i]) {
            int v = edge[i];
            d.add(son[v]);
        }
        d.sort(null);
        System.out.println(son[1] - d.get(d.size() - 1));
        in.close();
    }
}

E - Takahashi Quest

题目大意

你正在进行一场冒险,在这个冒险中有 \(N\) 个事件,事件有两种,1 x 表示你遇到了一瓶类型为 \(x\) 魔法药水,你可以选择拾取这个药水,这会占用你背包的 \(1\) 单位体积。2 x 表示你遇到了一直怪物,需要使用一瓶类型为 \(x\) 魔法药水才能战胜它。一瓶魔法药水瓶只能用一次,用过之后可以丢弃,不再占用背包的体积。同种类型的魔法药水可以拾取多瓶。问:为了战胜所有的怪物,最少需要多大容量的背包?如果无法战胜所有的怪物,输出 \(-1\)。如果可以战胜所有的怪物,第一行输出最小的背包容量,第二行输出遇到每个药水的时候是否拾取(1 表示拾取,0 表示不拾取)。

解题思路

之前做过一道 Expedition - POJ 2431,本题采用类似的思路。将遇到的药水存储起来(而不是立即捡起),在后面需要的时候再取出来使用。存储药水的时候可以开多个栈,每个栈存储同种类型的数组,需要用药水的时候从栈中弹出,这意味着取出的一定是最靠右的药水,可以证明这样做一定能让背包的容量最小。取出药水之后,在使用它之前都一直占用体积,用差分数组来实现区间加。最后还原出每个时间点的背包容量就是答案。总时间复杂度 \(O(n)\)

参考代码
#include <iostream>
#include <cstdio>
#include <stack>

using namespace std;

const int maxn = 2E5 + 10;

int n, b[maxn], t[maxn], x[maxn];
stack<int> st[maxn];
bool take[maxn];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d %d", &t[i], &x[i]);
    for (int i = 1; i <= n; i++)
    {
        if (t[i] == 1)
            st[x[i]].push(i);
        else
        {
            if (st[x[i]].empty())
            {
                puts("-1");
                return 0;
            }
            int p = st[x[i]].top();
            st[x[i]].pop();
            take[p] = 1;
            b[p]++;
            b[i]--;
        }
    }
    int M = -1;
    for (int i = 1; i <= n; i++)
    {
        b[i] += b[i - 1];
        M = max(M, b[i]);
    }
    cout << M << endl;
    for (int i = 1; i <= n; i++)
        if(t[i] == 1)
            printf("%d ", (int)take[i]);
    return 0;
}
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] b = new int[n + 1];
        int[] t = new int[n + 1];
        int[] x = new int[n + 1];
        boolean[] take = new boolean[n + 1];
        HashMap<Integer, Stack<Integer>> st = new HashMap<>();
        for (int i = 1; i <= n; i++) {
            t[i] = in.nextInt();
            x[i] = in.nextInt();
        }
        for (int i = 1; i <= n; i++) {
            if (t[i] == 1) {
                if (!st.containsKey(x[i]))
                    st.put(x[i], new Stack<>());
                st.get(x[i]).add(i);
            } else {
                if (!st.containsKey(x[i])) {
                    System.out.println(-1);
                    return;
                }
                int p = st.get(x[i]).pop();
                if (st.get(x[i]).isEmpty())
                    st.remove(x[i]);
                take[p] = true;
                b[p]++;
                b[i]--;
            }
        }
        int M = -1;
        for (int i = 1; i <= n; i++) {
            b[i] += b[i - 1];
            M = Math.max(M, b[i]);
        }
        System.out.println(M);
        for (int i = 1; i <= n; i++)
            if (t[i] == 1)
                System.out.printf("%d ", take[i] ? 1 : 0);
        in.close();
    }
}

F - Bomb Game 2

题目大意

\(N(1 \le N \le 3000)\) 个人排成一个队列玩游戏,重复一种操作:队首的人有 \(\frac{1}{2}\) 的概率出局,或者有 \(\frac{1}{2}\) 的概率从队首移动到队尾。剩一个人时游戏结束,最后一个人获胜。求出每个人获胜的数学期望,答案对 \(998244353\) 取模。

解题思路

先看看样例的 \(N = 2\) 是怎么算的,不妨设 A 和 B 留下的概率是 \(x\)\(y\),考虑第一次操作的结果,如果第一次操作是 A 出局,则 B 获胜,这个事件的概率是 \(\frac{1}{2}\)。如果第一次 A 没有出局,则队伍变成乙在队头,甲在队尾。这是一种类似的情形,只不过位置替换,借助递归的定义,可以列出方程:

\[ \left\{ \begin{matrix} x & = & \frac{1}{2}y \\ y & = & \frac{1}{2} + \frac{1}{2}x \end{matrix} \right. \Longrightarrow \left\{ \begin{matrix} x & = & \frac{1}{3} \\ y & = & \frac{2}{3} \end{matrix} \right. \]

接下来考虑用 dp 解决这个问题,定义 \(dp[i][j]\)\(i\) 个人的游戏中,第 \(j\) 个人能获胜的概率。

只需要考虑第一次操作的结果是否出局,可得:

\[ dp[i][j] = \left\{ \begin{matrix} \frac{1}{2}dp[i][i], & j = 1\\ \frac{1}{2}dp[i][j-1] + \frac{1}{2} * dp[i-1][j-1], & j > 1 \end{matrix} \right. \]

这个转移式有个问题,算出 \(dp[i][1]\) 需要引用 \(dp[i][i]\),而算出 \(dp[i][2]\) 需要引用 \(dp[i][1]\)\(\cdots \cdots\) ,这是一种循环引用的关系,不能直接递推。

仿照解方程的方法,设 \(dp[i][1], dp[i][2], \cdots, dp[i][i]\)\(x_1, x_2, \cdots, x_i\),根据转移式,有 \(i\) 个方程,\(i\) 个变量,解方程的话是肯定可以解出来的。但是使用高斯消元解方程的复杂度达到 \(O(n^3)\),必定超时。

注意到这里的方程是很简单的形式,所以可以优化。

观察 dp转移式,当 \(j > 1\) 时,\(\frac{1}{2} * dp[i-1][j-1]\) 的部分实际是一个常数,上一层就已经算出来了。而 \(\frac{1}{2}dp[i][j-1]\) 是上一个变量的线性倍,即:\(x_i = ax_{i-1} + b\),这就可以通过一种简便的方法来解方程。

例如 \(N = 3\) 的时候:

\[ \left\{ \begin{matrix} x_2 &=& \frac{1}{2}x_1 + \frac{1}{6} \\ x_3 &=& \frac{1}{2}x_2 + \frac{1}{3} &=& \frac{1}{4}x_1 + \frac{5}{12} \\ \end{matrix} \right. \]

又因为:

\[ x_1 = \frac{1}{2}x_3 = \frac{1}{8}x_1 + \frac{5}{24} \\ \]

最终解得:

\[ x_1 = \frac{5}{21} \]

然后回代即可得到 \(x_2\)\(x_3\)

这个方法的核心就是因为 \(x_i = ax_{i-1} + b\) 的形式,所以我们可以只维护 \(a\)\(b\) 的系数值,然后解出 \(x_1\),这是非常容易的,显然,无论是求解 \(x_1\) 还是回代的过程都是 \(O(n)\) 的,这样总的复杂度就是 \(O(n^2)\)

参考代码
#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;
const int maxn = 3000 + 10;
const LL mod = 998244353;

void exgcd(LL a, LL b, LL &x, LL &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return;
    }
    exgcd(b, a%b, y, x);
    y -= a / b * x;
}

LL inv(LL a)
{
    LL x, y;
    exgcd(a, mod, x, y);
    if(x < 0)
        x += mod;
    return x;
}

LL mul(LL a, LL b)
{
    a *= b;
    a %= mod;
    return a;
}

int n;
LL dp[maxn][maxn];

// dp[i][j] = 1/2 * dp[i][j-1] + 1/2 * dp[i-1][j-1]
// dp[i][1] = 1/2 * dp[i][i]

int main()
{
    cin >> n;
    LL p = inv(2);
    dp[1][1] = 1;
    for(int i = 2; i <= n; i++)
    {
        LL a = 1, b = 0;
        for(int j = 2; j <= i; j++)
        {
            b += dp[i-1][j-1];
            b %= mod;
            a = mul(a, p);
            b = mul(b, p);
        }
        a = mul(a, p);
        b = mul(b, p);
        dp[i][1] = mul(b, inv((1 - a + mod) % mod));
        for(int j = 2; j <= i; j++)
            dp[i][j] = mul(p, dp[i][j-1]+dp[i-1][j-1]);
    }
    for(int i = 1; i <= n; i++)
        printf("%lld ", dp[n][i]);
    return 0;
}
import java.util.Scanner;

public class Main {
    final private static long mod = 998244353;

    private static long[] exgcd(long a, long b) {
        if (b == 0)
            return new long[] { 1, 0 };
        long[] xy = exgcd(b, a % b);
        long x2 = xy[0], y2 = xy[1];
        xy[0] = y2;
        xy[1] = x2 - a / b * y2;
        return xy;
    }

    private static long inv(long a) {
        long x = exgcd(a, mod)[0];
        if (x < 0)
            x += mod;
        return x;
    }

    private static long mul(long a, long b) {
        a *= b;
        a %= mod;
        return a;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        long[][] dp = new long[n + 1][n + 1];
        long p = inv(2);
        dp[1][1] = 1;
        for (int i = 2; i <= n; i++) {
            long a = 1, b = 0;
            for (int j = 2; j <= i; j++) {
                b += dp[i - 1][j - 1];
                b %= mod;
                a = mul(a, p);
                b = mul(b, p);
            }
            a = mul(a, p);
            b = mul(b, p);
            dp[i][1] = mul(b, inv((1 - a + mod) % mod));
            for (int j = 2; j <= i; j++)
                dp[i][j] = mul(p, dp[i][j - 1] + dp[i - 1][j - 1]);
        }
        for (int i = 1; i <= n; i++)
            System.out.println(dp[n][i]);
        in.close();
    }
}

G - Nearest Fraction

题目大意

给你一个实数 \(r(0 < r < 1)\) 和一个整数 \(N(1 \le 10^{10})\),构造一个最接近的 \(r\) 的最简分数 \(\frac{p}{q}\),其中 \(0 \le p \le q \le N\) 。换言之,构造一个 \(|r - \frac{p}{q}|\) 最小的 \(\frac{p}{q}\),输出 \(p\)\(q\) 的值,如果有多个答案,输出 \(\frac{p}{q}\) 较小的那个。

ps:不会做,改天补一下这道题......


最后更新: 2024-03-04
创建日期: 2024-03-01