ACM训练赛:第20次

这次的题思维都很强,等之后的考试结束会集中精力重新训练一些思维题。

A - A simple question

CodeForces - 520B

思路:

直接看的话,很容易发现如果 \(n >= m\) 的话 \(sum = n - m\) 即可,但反过来其实 \(m\) 推导 \(n\) 更简单(WA几发后才发现。。)

如果 \(m\) 为偶数的话 缩小一半,不然的话先变为偶数再除以2。这样一定能变为 \(n\)

void solve() {
    int n, m;
    cin >> n >> m;
    int sum = 0;
    if (n >= m)
        sum = n - m;
    else {
        while (n != m) {
            if (m % 2 == 0 && m > n)
                m = m / 2, sum++;
            else if (n >= m) {
                sum = sum + n - m;
                break;
            }
            if (m % 2 != 0 && m > n)
                m = (m + 1) / 2, sum = sum + 2;
        }
    }
    cout << sum << endl;
}

B - Game

Gym - 102822G (出处2020 CCPC绵阳站)

题解思路参考:

看起来是博弈对吧?对,的确是使用SG函数,但蒟蒻表示不会,只能一个个模拟情况,赛后看了下这道题的官方题解(没想到也还是一道模拟找规律的题2333)

官方题解截图

image-20201209184114610

// 这里用的是解法二
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll c0, c1, c2, c3;
int Case = 1;
int check() {
    if (c0 == 0 && c1 == c0 && c2 == 0 && c3 == 0)
        return 0;
    int f = 1;
    if (c3 == 0) {
        if (c1 == 0 && c2 == 0) {
            if (c0 == 0 || c0 % 2)
                f = 0;
        } else {
            if (c0 % 2 == 0) {
                if (c1 % 3 == 0)
                    f = 0;
                else {
                    if (c1 % 3 == 1 && c2 == 0)
                        f = 0;
                }
            } else {
                if (c1 % 3 == 1 && c2 > 0)
                    f = 0;
                else if (c1 % 3 == 2 && c2 <= 1)
                    f = 0;
            }
        }
    } else {
        if (c0 % 2 == 0) {
            if (c1 % 3 == 0)
                f = 0;
            else if (c1 % 3 == 1 && c2 == 0)
                f = 0;
        } else {
            if (c1 % 3 == 1 && c2 > 0)
                f = 0;
            else if (c1 % 3 == 2 && c2 <= 1)
                f = 0;
        }
    }
    return f;
}
void solve() {
    cin >> c0 >> c1 >> c2 >> c3;
    int Win_one = check();
    cout << "Case #" << Case++ << ": ";
    if (Win_one == 1)
        cout << "Rabbit\n";
    else
        cout << "Horse\n";
}
int main() {
    // freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int _;
    cin >> _;
    while (_--)
        solve();
}

看博客的时候发现有人用DFS写了下,感觉也行。下面贴代码

Code
int dfs(int c0, int c1, int c2, int c3) {
    if (c0 == 0 && c1 == 0)
        return 0;
    if (c0 == 0 && c2 == 0 && c1 < 2)
        return 0;
    if (c0 > 0 && !dfs(c0 - 1, c1, c2, c3))
        return 1;
    if (c1 >= 2 && !dfs(c0, c1 - 2, c2 + 1, c3))
        return 1;
    if (c1 > 0 && c2 > 0 && !dfs(c0, c1 - 1, c2 - 1, c3 + 1))
        return 1;
    return 0;
}
int main() {
    int t;
    scanf("%d", &t);
    int cas = 0;
    while (t--) {
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        int win = -1;
        if (b == 0 && c == 0 && d == 0) {
            if (a == 0)
                win = 0;
            else if (a & 1)
                win = 0;
            else
                win = 1;
        } else
            win = dfs(a % 2, b % 3, c, d);
        printf("Case #%d: %s\n", ++cas, win ? "Rabbit" : "Horse");
    }
    return 0;
}

C - CCCCC

题目链接: Gym - 102798D (出处:2020 CCPC威阳站)

思路图来自南京大学题解 ↓(感谢分享)

题意:给定一个数 C(\(1 \sim 1e18\)),找到一组 \(a,b\) 使得 \(a + b = c\ 且\ abc\ 的素数因子要求 < C\) ,如果存在这样的 \(a,b\) 的话输出 yes,不然输出 no

其实上方的图片中提到的解决方法已经很好了,现在翻译一下 ↓

解决方法(翻译):

  • 如果 c 无平方因子,那么是不会存在任意一组 a,b的,即可以直接输出 no
  • 如果 c 包含平方因子,即 :$c = p^2q\ (p > 1) $ ,所以可以令 \(a = pq\) , \(b = p(p -1)q\) ,使得 \(a + b = c\) ,并且 \(rad(abc) = rad(p^4(p -1)q^3) \leq rad(p(p - 1)q) \leq p^2q = c\)

要检查是否有无平方,则要要检查 \(p^2\) 直到 \(√{3c}\)\(\sqrt{c}\) 的平方整数

\(O(√3c)\)

// Author : RioTian
// Time : 20/12/09
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e7 + 10;
ll _, c;
int cnt, prime[N];
bool st[N];
// 线性筛筛素数
void init(ll n) {
    for (int i = 2; i <= n; ++i) {
        if (!st[i])
            prime[++cnt] = i;
        for (int j = 1; prime[j] <= n / i; ++j) {
            st[i * prime[j]] = true;
            if (i % prime[j] == 0)
                break;
        }
    }
}
void solve() {
    cin >> c;
    bool f = false;
    for (int i = 1; i <= cnt && !f; ++i) {
        ll p = prime[i];
        int s = 0;
        if (c % p == 0)
            while (c % p == 0)
                c /= p, s++;
        if (s >= 2)
            f = true;
    }
    ll x = sqrt(c);
    if (c > 1 && 1ll * x * x == c)
        f = true;
    if (f)
        cout << "yes\n";
    else
        cout << "no\n";
}
int main() {
    freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init(N - 10);
    for (cin >> _; _; _--)
        solve();
}

D - String

计蒜客 - T2652

头铁记:一看到字符串就想到然往字符串方向走,但没想到是类搜索 2333

这里贴下学长的思路(绝不是懒的打字)

// Author : RioTian
// Time : 20/12/09
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int mod = 20100403;
ll qpow(ll a, ll b) {
    ll ans = 1;
    a %= mod;
    for (; b; a = a * a % mod, b >>= 1)
        if (b & 1)
            ans = ans * a % mod;
    return ans;
}
ll C(ll n, ll m) {
    ll x = 1, y = 1;
    for (int i = 1; i <= m; ++i) {
        x = x * (n - i + 1) % mod;
        y = y * i % mod;
    }
    return x * qpow(y, mod - 2) % mod;
}

int main() {
    // freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    ll n, m;
    cin >> n >> m;
    cout << (C(m + n, m) - C(m + n, m - 1) % mod + mod) % mod;
}

E - Probability

AtCoder - abc184_d

题意:一个包里包含 X 个金币、Y 个银币、Z 个铜币。在包里钱币满足相 同颜色达到 100 之前,我们可以重复以下动作:随机选一种钱币,取出一枚, 再放入相同颜色钱币两枚。找出完成这些操作的期望值。

根据题目的意思,其实就是每次向包里随机加入一枚钱币,直到包里某种钱币数量达到 100。本题的核心是如何计算期望。本题属于标准的动态规划求期望问题。直接套用模板即可。

一道”简单“概率DP题,没怎么了解概率DP导致做不出

先贴一下学长给的概率DP知识点博客:Here

当理解基础的知识以后发现的确比较简单

DP 数组定义

定义 DP[i][j][k],表示有 i 枚金币, j 枚银币, k 枚铜币的期望。

初值

所有的期望都为零。

递推方法

使用逆推。

状态转移方程:

\[s = X + Y + Z\\ dp(i,j,k) = \frac{i}{s}*(dp(i + 1,j,k) + 1) + \frac{j}{s}*dp(i,j + 1,k) + 1) \\+ \frac{j}{s}*dp(i,j,k + 1) + 1)) \]

AC代码:

// Author : RioTian
// Time : 20/12/09
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e2 + 10;
double dp[N][N][N];
int main() {
    // freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int a, b, c;
    cin >> a >> b >> c;
    for (int i = 99; i >= a; i--)
        for (int j = 99; j >= b; j--)
            for (int k = 99; k >= c; k--) {
                // 令 t = x + y + z,减少代码量
                double t = i + j + k;
                dp[i][j][k] = i / t * (dp[i + 1][j][k] + 1) +
                              j / t * (dp[i][j + 1][k] + 1) +
                              k / t * (dp[i][j][k + 1] + 1);
            }
    // 关于 C++ 的输出控制可以在我的以前博客找到
    cout << fixed << setprecision(9) << dp[a][b][c] << endl;
}

时间和空间复杂度:

\(O(n^3)\)

学习的时候发现一种模拟方法:蒙特卡洛方法模拟

使用蒙特卡洛方法模拟

计划等有空好好学一下这个

本题核心其实是一个随机模拟过程,因此也可以使用蒙特卡洛方法(Monte Carlo method)来模拟这个过程。首先就不证明这个过程是收敛的,说真的,我也不大会证明,以后努力。具体的 Monte Carlo method 请看相关资料。

因此,我们只需要模拟这样的过程,只需要足够的样本数量就可以完成模拟。我这里用了 300 次就可以满足题目的需求。为什么 300 就够了,其实就是反复测试出来的。

AC代码

// increment of coins
// Using Monte Carlo method
// Author : RioTian
// Time : 20/12/09
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

double prob(int n, int a, int b, int c) {
    int u = 100 - a;
    if (u > n)
        return 0.0;
    if (n - u > (99 - b) + (99 - c))
        return 0.0;

    double p = 1.0, ret = 0.0;
    for (int i = 0; i < u; i++)
        p *= 1.0 * (a + i) / (a + b + c + i);

    for (int v = 0; v <= n - u; ++v) {
        if (v + b > 99 || n - u - v + c > 99)
            continue;
        double exp = 1.0;
        for (int j = 0; j < v; j++)
            exp *= 1.0 * (b + j) / (a + b + c + u + j);

        for (int j = 0; j < n - u - v; j++)
            exp *= 1.0 * (c + j) / (a + b + c + u + v + j);

        for (int j = v; j >= 1; j--)
            exp *= 1.0 * (u - 1 + j) / j;

        for (int j = n - u - v; j >= 1; j--)
            exp *= 1.0 * (u - 1 + v + j) / j;

        ret += (p * exp);
    }
    return ret;
}

int main() {
    // freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int a, b, c;
    cin >> a >> b >> c;

    double ans = 0;
    for (int i = 1; i <= 300; ++i) {
        double pa = prob(i, a, b, c);
        double pb = prob(i, b, c, a);
        double pc = prob(i, c, a, b);
        ans += i * (pa + pb + pc);
    }

    cout << fixed << setprecision(9) << ans << endl;
}

时间复杂度

\(O(n)\)。虽然也是三重循环,我们要注意到循环的次数是常数项。比如 main() 中的循环是固定 300 次,prob() 中的循环最多是 100 次。所以乘积还是常数次。

空间复杂度

\(O(n)\)

posted @ 2020-12-09 20:58  Koshkaaa  阅读(229)  评论(0编辑  收藏  举报