AtCoder Beginner Contest 243 题解(D~G)

D - Moves on Binary Tree

https://atcoder.jp/contests/abc243/tasks/abc243_d
题意: 有一颗以1为根的二叉树,节点个数无穷大,按照字符串S对节点x进行操作,跳到当前的父节点(U),跳到左儿子(L),跳到右儿子®。
题解: 因为在过程中的数据可能会很大很大,不能直接模拟。我们能够发现一次向儿子跳和一次向父亲跳相当于没有进行操作,然后就可以用栈进行模拟啦~
代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes "Yes\n"
#define no "No\n"
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
typedef long double db;

int n, m, a[N], x;
void solve() {
    cin >> n >> x;
    stack<char> st;
    string s;
    cin >> s;
    for (int i = 0; i < n; i++) {
        if (s[i] == 'U') {
            if (!st.empty() && st.top() != 'U')  // 向儿子走和上一个向父亲走抵消掉了
                st.pop();
            else
                st.push(s[i]);
        } else {
            st.push(s[i]);
        }
    }
    vector<char> v;
    while (st.size()) {
        v.push_back(st.top());
        st.pop();
    }
    reverse(v.begin(), v.end());  // 翻转下
    for (auto it : v) {           // 答案保证在1e18内,然后就可以直接模拟了
        if (it == 'U')
            x /= 2;
        else if (it == 'L')
            x *= 2;
        else
            x = x * 2 + 1;
    }
    cout << x;
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int T = 1;
    // cin >> T;
    for (int i = 1; i <= T; i++) {
        solve();
    }
}

也可以用栈模拟二进制,碰到U时相当于右移,将栈顶弹出;碰到L时在栈顶加入0;碰到R在栈顶加入1.

E - Edge Deletion

https://atcoder.jp/contests/abc243/tasks/abc243_e
题意: 有一个n个点m条边的无向连通图,在保证联通并且任意两点的最短路距离不变的情况下,最多能够删掉多少条边。 N < = 300 N<=300 N<=300
题解: 看到这个N的数据范围,又是多源最短路径相关的问题,大概都能想到 f l o y d floyd floydhttps://zhuanlan.zhihu.com/p/405188391 floyd参考文章
我们先通过 f l o y d floyd floyd求出任意两点间的最短路,然后再判断每条边能否删掉:
易知当一条边的边长比两点的最短路还要长肯定是能删掉的,因为它对最短路不会产生任何贡献。
当边长等于最短路径长度的时候,就需要判断这条边是不是这两个点唯一的最短路径,我们可以枚举中转点是否存在通过中转点得到最短路径
代码:

struct node {
    int x, y, z;
};
vector<node> ed;
int dis[N][N];
void solve() {
    cin >> n >> m;
    memset(dis, 0x3f, sizeof dis);  // 初始化
    for (int i = 1; i <= n; i++)
        dis[i][i] = 0;
    for (int i = 1; i <= m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        dis[x][y] = dis[y][x] = z;
        ed.push_back({x, y, z});  // 存边
    }
    for (int k = 1; k <= n; k++) {// flody(一定要先枚举中转点,因为floyd本质是dp)
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
            }
        }
    }
    int ans = 0;
    for (auto it : ed) {  // 枚举边
        int x = it.x, y = it.y, z = it.z;
        if (dis[x][y] < z) {  // z比x-y的最短路径长
            ans++;
        } else if (dis[x][y] == z) {
            for (int i = 1; i <= n; i++) { // 枚举中转点
                if (i != x && i != y && dis[x][i] + dis[i][y] == z) { // 存在除x-y这条边的最短路径
                    ans++;
                    break;
                }
            }
        }
    }
    cout << ans << endl;
}

F - Lottery

https://atcoder.jp/contests/abc243/tasks/abc243_f
题意: 每画一幅画能够得到一种奖品,得到第i种奖品的概率是 W i ∑ j = 1 N W j \frac{W_{i}}{\sum_{j=1}^{N} W_{j}} j=1NWjWi,画K幅画得到准确的m种奖的概率是多少。
1 < = N , M , K < = 50 1<=N,M,K<=50 1<=N,M,K<=50
题解
概率dp+组合数学
假设K次选到的奖依次是的概率 a [ 1 ] , a [ 2 ] , . . . . . . a [ k ] a[1],a[2],......a[k] a[1],a[2],......a[k],那它的概率是 ∏ i = 1 K a [ i ] \prod_{i=1}^{K}a[i] i=1Ka[i]
那假设选的M种概率分别为 b [ 1 ] , b [ 2 ] , . . . , b [ M ] b[1],b[2],...,b[M] b[1],b[2],...,b[M],选的个数依次为 c [ 1 ] , c [ 2 ] , . . . , c [ M ] c[1],c[2],...,c[M] c[1],c[2],...,c[M]
它的概率为 K ! ∏ i = 1 M ( c [ i ] ! ) ∏ i = 1 M b [ i ] c [ i ] \frac{K!}{\prod_{i=1}^{M} (c[i]!)} \prod_{i=1}^{M}b[i]^{c[i]} i=1M(c[i]!)K!i=1Mb[i]c[i],也即 K ! ∏ i = 1 M b [ i ] c [ i ] c [ i ] ! K!\prod_{i=1}^{M}\frac{b[i]^{c[i]}}{c[i]!} K!i=1Mc[i]!b[i]c[i]
定义 d p [ i ] [ j ] [ l ] : dp[i][j][l]: dp[i][j][l] 前i个,有j种不同的奖,总共化了l张的概率
转移方程:
通过枚举r(第i种选的个数)来进行转移
如果取了第i种:
d p [ i ] [ j ] [ l ] + = d p [ i − 1 ] [ j − 1 ] [ l − r ] ∗ a [ i ] r r ! dp[i][j][l] += dp[i - 1][j - 1][l - r] * \frac{a[i]^r} {r!} dp[i][j][l]+=dp[i1][j1][lr]r!a[i]r
如果没取第i种:
d p [ i ] [ j ] [ l ] + = d p [ i − 1 ] [ j ] [ l − r ] ∗ a [ i ] r r ! dp[i][j][l] += dp[i - 1][j][l - r] * \frac{a[i]^r} {r!} dp[i][j][l]+=dp[i1][j][lr]r!a[i]r
代码:

int n, m, a[N], k, dp[N][N][N];  // 前i个,有j个不同,总共拿了l个
int qpow(int a, int n) {
    int ans = 1;
    while (n) {
        if (n & 1) {
            ans = ans * a % mod;
        }
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}
int fac[N], ifac[N];
void add(int& x) {  // 取模
    x = (x % mod + mod) % mod;
}
void init() {
    fac[0] = ifac[0] = 1;
    for (int i = 1; i < N; ++i)
        fac[i] = fac[i - 1] * i % mod;  // 阶乘
    ifac[N - 1] = qpow(fac[N - 1], mod - 2);
    for (int i = N - 2; i; --i)  // 阶乘的逆元
        ifac[i] = ifac[i + 1] * (i + 1) % mod;
}
void solve() {
    cin >> n >> m >> k;
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum += a[i];
    }
    int inv = qpow(sum, mod - 2);
    for (int i = 1; i <= n; i++)
        a[i] = a[i] * inv % mod;  // 拿到第i种的概率
    dp[0][0][0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            for (int l = 0; l <= k; l++) {
                for (int r = 0; r <= l; r++) {  // 第i种拿多少个
                    if (j == 0 && r > 0) {      // 不能取i,不合法
                        continue;
                    }
                    if (r >= 1)
                        dp[i][j][l] += dp[i - 1][j - 1][l - r] * qpow(a[i], r) % mod * ifac[r] % mod;
                    else
                        dp[i][j][l] += dp[i - 1][j][l - r] * qpow(a[i], r) % mod * ifac[r] % mod;
                    add(dp[i][j][l]);
                }
            }
        }
    }
    cout << dp[n][m][k] * fac[k] % mod;
}

G - Sqrt

https://atcoder.jp/contests/abc243/tasks/abc243_g
题意:
有一个序列A,初始只有X一个数,每次可以将A末尾的数(记为Y) [ 1 , Y ] [1,\sqrt{Y}] [1,Y ]中任意一个数加到A的末尾,问最后的不同序列的种类是多少
题解:
很显然,当序列末尾的数为1之后后面都是1了,也就序列之后的不会再不同了。
先考虑一个数x能产生的不同序列种类(记为f[x]),易知 f [ x ] = ∑ i = 1 x f [ i ] f[x]=\sum_{i=1}^{\sqrt{x}}f[i] f[x]=i=1x f[i]
问题就转化成了求 ∑ i = 1 x f [ x ] \sum_{i=1}^{\sqrt{x}}f[x] i=1x f[x],暴力求解显然是根号级别,是不够的。
通过计算前几个数的 f [ i ] f[i] f[i],我们能够发现一些连续的数的f是相等的,因为根号是向下取整,一个范围内的根号是相等的,f的值自然也是相等的。那么我们就可以将 X \sqrt{X} X 分成一个个 [ i ∗ i , ( i + 1 ) ∗ ( i + 1 ) − 1 ] [i*i,(i+1)*(i+1)-1] [ii,(i+1)(i+1)1]的区间,复杂度就变成了 O ( X 1 4 ) O(X^{\frac{1}{4}}) O(X41)
代码:

#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define yes "Yes\n"
#define no "No\n"
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>
typedef long double db;

int n, m, a[N], f[N], g[N];
// f[i]:数i产生的不同序列个数,g[i]:f的前缀和[1,i]产生的不同序列的个数
void solve() {
    db n;//因为题目范围是9e18,所以用long double了
    cin >> n;
    int t = (int)sqrt(n), ans = 0;
    for (int i = 1; i * i <= t; i++) {
        ans += (min(t, (i + 1) * (i + 1) - 1) - i * i + 1) * g[i];
        //[i*i,(i+1)*(i+1)-1]区间内的根号值都相同且等于i,产生的不同序列也就是g[i]
    }
    cout << ans << "\n";
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    f[1] = 1;
    g[1] = 1;
    for (int i = 1; i < N; i++) {
        int t = sqrt(i);
        g[t] = g[t - 1] + f[t];  // 更新前缀和,为后面f的计算
        f[i] = g[t];             // f[i]为[1,sqrt(i)]的f的和,也即g[sqrt(i)]
    }
    g[1] = 1;
    for (int i = 2; i < N; i++) {  // 计算前缀和,有些前缀并没有计算完
        g[i] = g[i - 1] + f[i];
    }
    int T = 1;
    cin >> T;
    for (int i = 1; i <= T; i++) {
        solve();
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self_disc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值