Codeforces Round 926(Div.2) A~F

A.Sasha and the Beautiful Array(递推)

题意:

萨沙决定送给女友一个数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an。他发现女友会将数组的美丽度评估为所有从 2 2 2 n n n的整数 i i i ( a i − a i − 1 ) (a_i−a_{i−1}) (aiai1)之和。

如果可以重新排列其元素,请帮助他计算数组 a a a的最大美丽度是多少。

分析:

递推可以发现最后的结果为最后一个元素减第一个元素,将其分别取最大和最小,此时的美丽度就是最大值。

代码:

#include<bits/stdc++.h>

using namespace std;
int a[105];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
            cin >> a[i];
        sort(a, a + n);
        cout << a[n - 1] - a[0] << endl;
    }
    return 0;
}

B.Sasha and the Drawing(模拟)

题意:

在幼儿园时,萨沙就喜欢上了一个女孩。他想给她画一幅画,吸引她的注意。

他决定画一个大小为 n × n n\times n n×n的正方形网格,在网格中给一些单元格涂上颜色。但是给单元格上色很困难,所以他想给尽可能少的单元格上色。但与此同时,他希望至少 k k k条对角线上至少有一个着色的单元格。大小为 n × n n\times n n×n的正方形网格共有 4 n − 2 4n-2 4n2条对角线。

计算他需要涂色的最小单元格数。

分析:

模拟一下发现前几次操作可以使答案增加 2 2 2,但是在到达一定的数量后每次只能让答案增加 1 1 1。考虑如何计算出最少涂多少个能把贡献为 2 2 2的格子涂完。画几个图手推一下发现最优方案是先填满第一列,再从最后一列第二行填到倒数第二行,这样前 2 × ( n − 1 ) 2\times(n-1) 2×(n1)次的贡献都是 2 2 2

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        if (k <= (n + n - 2) * 2) {
            cout << (k + 1) / 2 << endl;
            continue;
        }
        int ans = 2 * n - 2;
        k -= ans * 2;
        cout << ans + k << endl;
    }
    return 0;
}

C.Sasha and the Casino(递推)

题意:

萨沙决定送给女朋友一个最好的手提包,但不幸的是,这个手提包非常昂贵。因此,萨沙想赚点钱。在网上查看了赚钱技巧后,他决定去赌场。

萨沙知道赌场的运作规则如下。如果萨沙下注 y y y个硬币(其中 y y y为正整数),如果他赢了,他将获得 y ⋅ k y\cdot k yk个硬币(即他的硬币数量将增加 y ⋅ ( k − 1 ) y\cdot(k-1) y(k1))。如果输了,他将输掉全部赌注(即他的硬币数量将减少 y y y)。

注意,投注金额必须始终是一个正整数,并且不能超过萨沙当前的硬币数量。

萨沙还知道赌场有一个保底活动:他不会连续输超过 x x x次。

最初,萨沙有 a a a枚硬币。他想知道自己是否可以下注保证赢取任意数量的硬币。换句话说,对于任意整数 n n n,萨沙可以进行下注,以便在不违反上述规则的情况下,在某一时刻至少拥有n个硬币。

分析:

本题类似倍投法,在一赔一的情况下,第一次压一块钱,每输一次就押注上一次两倍的金额。假如本金无限的话,这种方法赢的期望为无穷大。因为无论输多少次,赢一次本金就增加了 1 1 1

考虑到最坏情况,即第 x + 1 x+1 x+1次才获胜。如果第 x + 1 x+1 x+1次赚钱了,并且投进去的总金额小于等于自己拥有的本金,然后再进行无限多次即可赢取任意数量的钱。所以只需要判断连续输 x x x次是否会输光本金即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int k, x, a;
        cin >> k >> x >> a;
        LL sum = 0;
        bool flag = true;
        for (int i = 1; i <= x + 1; i++) {
            LL tmp = (sum + 1 + k - 2) / (k - 1);
            sum += tmp;
            if (sum > a) {
                flag = false;
                break;
            }
        }
        if (flag) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

D.Sasha and a Walk in the City(动态规划)

题意:

萨沙想和他的女朋友在城市里散步。城市由 n n n个路口组成,编号从 1 1 1 n n n。其中一些路口由道路连接,从任何一个路口出发,都有一条简单的路径 † ^{\dagger} 通往其他路口。换句话说,十字路口和它们之间的道路组成了一棵树。

有些交叉路口是危险的。由于在城市中独自行走很不安全,所以萨沙不想在行走过程中经过三个或三个以上的危险路口。

如果满足以下条件,萨沙就会将一组路口称为好路口:

  • 如果在城市中只有这组交叉路口是危险的,那么城市中的任何一条简单路径都包含不超过两个危险的交叉路口。

然而,萨沙并不知道哪些交叉路口是危险的,因此他感兴趣的是城市中不同的好的交叉路口集的数量。计算这个数量并对 998244353 998244353 998244353取模。

† ^{\dagger} 简单路径是指最多经过每个交叉路口一次的路径。

分析:

我们定义 f ( u , 0 / 1 / 2 ) f(u,0/1/2) f(u,0/1/2)表示以 u u u为根的子树中, u u u到每个节点路径中最多有 0 / 1 / 2 0/1/2 0/1/2个被选择的点时,(满足题目要求的)合法点集的数量。

对于 f ( u , 0 ) f(u,0) f(u,0),即子树 u u u内任何节点都不会被选,有 f ( u , 0 ) = 1 f(u,0)=1 f(u,0)=1

对于 f ( u , 1 ) f(u,1) f(u,1),如果不选 u u u,那么每个子树 v i v_i vi内的点到 v i v_i vi的路径中既可以有 0 0 0个或 1 1 1个被选择的点(保证经过 u u u的简单路径中被选择的点不超过 2 2 2个),那么方案数就是 ( ∏ f ( v i , 0 ) + f ( v i , 1 ) ) − 1 \left(\prod{f(v_i,0)+f(v_i,1)}\right)-1 (f(vi,0)+f(vi,1))1,减 1 1 1指减去均取 f ( v i , 0 ) f(v_i,0) f(vi,0)的情况,因为要保证到 u u u的路径中最多有 1 1 1个被选择的点。如果选 u u u,那么每个子树 v i v_i vi中不能再选择点,只有 ∏ f ( v i , 0 ) = 1 \prod{f(v_i,0)}=1 f(vi,0)=1种方案。因此 f ( u , 1 ) = ∏ f ( v i , 0 ) + f ( v i , 1 ) − 1 + 1 f(u,1)=\prod{f(v_i,0)+f(v_i,1)}-1+1 f(u,1)=f(vi,0)+f(vi,1)1+1

对于 f ( u , 2 ) f(u,2) f(u,2),如果不选 u u u,那么必须恰好有一个子树 v i v_i vi f ( v i , 2 ) f(v_i,2) f(vi,2)的状态,方案数是 ∑ f ( v i , 2 ) \sum{f(v_i,2)} f(vi,2)。如果选 u u u,也是必须恰好有一个子树 v i v_i vi f ( v i , 1 ) f(v_i,1) f(vi,1)的状态,方案数就是 ∑ f ( v i , 1 ) \sum{f(v_i,1)} f(vi,1)。因此 f ( u , 2 ) = ∑ f ( v i , 2 ) + f ( v i , 1 ) f(u,2)=\sum{f(v_i,2)+f(v_i,1)} f(u,2)=f(vi,2)+f(vi,1)

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 3e5 + 10;

LL dp[N][3], n;
vector<int> g[N];

void dfs(int u, int fa) {
    LL cur[3][2];
    memset(cur, 0, sizeof(cur));
    cur[0][0] = 1;
    for (auto v: g[u]) {
        if (v == fa)
            continue;
        dfs(v, u);
        LL tmp[3][2];
        memcpy(tmp, cur, sizeof(cur));
        memset(cur, 0, sizeof(cur));
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2 - i; j++) {
                if (i < j) {
                    cur[j][0] = (cur[j][0] + 1ll * (tmp[i][0] + tmp[i][1]) * dp[v][j] % mod) % mod;
                } else if (i == j) {
                    cur[i][1] = (cur[i][1] + 1ll * (tmp[i][0] + tmp[i][1]) * dp[v][j] % mod) % mod;
                } else {
                    cur[i][0] = (cur[i][0] + 1ll * tmp[i][0] * dp[v][j] % mod) % mod;
                    cur[i][1] = (cur[i][1] + 1ll * tmp[i][1] * dp[v][j] % mod) % mod;
                }
            }
        }
    }
    dp[u][0] = (cur[0][0] + cur[0][1]) % mod;
    dp[u][1] = (cur[1][0] + cur[1][1] + dp[u][0]) % mod;
    dp[u][2] = (cur[2][0] + cur[2][1] + cur[1][0]) % mod;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        cin >> n;
        for (int i = 2; i <= n; i++) {
            int u, v;
            cin >> u >> v;
            g[u].emplace_back(v);
            g[v].emplace_back(u);
        }
        dfs(1, 0);
        cout << (dp[1][0] + dp[1][1] + dp[1][2]) % mod << endl;
        for (int i = 1; i <= n; i++)
            g[i].clear();
    }

    return 0;
}

E.Sasha and the Happy Tree Cutting(动态规划)

题意:

萨沙获得了一棵有 n n n个顶点的树 † ^{\dagger} ,作为他又一次赢得比赛的奖品。然而,庆祝胜利回家后,他发现树的某些部分不见了。萨沙记得他给这棵树的一些边涂了颜色。他可以肯定,在 k k k对顶点 ( a 1 , b 1 ) , … , ( a k , b k ) (a_1,b_1),\ldots,(a_k,b_k) (a1,b1),,(ak,bk)中,他至少为顶点 a i a_i ai b i b_i bi之间的简单路径 ‡ ^{\ddagger} 上的一条边涂了颜色。

萨沙不记得他到底给多少条边涂了颜色,请你计算,为了满足上述条件,他至少需要给多少条边涂颜色。

† ^{\dagger} 树是没有循环的不定向连接图。

‡ ^{\ddagger} 简单路径是指每个顶点最多经过一次的路径。

分析:

观察数据范围,发现 k < = 20 k<=20 k<=20,考虑状压。考虑给每条边标记一个 T i T_i Ti表示有哪些关键路径经过它,设 d p i , S dp_{i,S} dpi,S表示处理完前 i i i条边,已经满足了 S S S的条件时需要选择的最小边数。

状态转移方程为: d p i + 1 , S ∪ T i ⇐ d p i , S + 1 dp_{i+1,S\cup T_i}\Leftarrow dp_{i,S}+1 dpi+1,STidpi,S+1

观察到 T i T_i Ti不同的边只有 O ( K ) O(K) O(K)条(考虑对 2 k 2k 2k个关键点建立虚树,虚树上的边数即为 T i T_i Ti不同的边数,而虚树的边数为 O ( K ) O(K) O(K),排序去重后 D P DP DP即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 3e5 + 10;

int n, k, val[N], dep[N], f[N], dp[2][1 << 20];
vector<int> g[N];

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    f[u] = fa;
    for (auto v: g[u]) {
        if (v == fa)
            continue;
        dfs(v, u);
    }
}

void solve() {
    cin >> n;
    for (int i = 2; i <= n; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    cin >> k;
    for (int i = 0; i < k; i++) {
        int a, b;
        cin >> a >> b;
        if (dep[a] < dep[b])
            swap(a, b);
        while (dep[a] > dep[b]) {
            val[a] |= 1 << i;
            a = f[a];
        }
        while (a != b) {
            val[a] |= 1 << i;
            val[b] |= 1 << i;
            a = f[a], b = f[b];
        }
    }
    sort(val + 1, val + n + 1);
    int cnt = unique(val + 1, val + n + 1) - val - 1;
    memset(dp[1], 0x3f, sizeof(int) * (1 << k));
    dp[1][0] = 0;
    for (int i = 1; i <= cnt; i++) {
        memcpy(dp[(i + 1) & 1], dp[i & 1], sizeof(int) * (1 << k));
        for (int s = 0; s < (1 << k); s++) {
            if (dp[i & 1][s] >= 0x3f3f3f3f)
                continue;
            int t = s | val[i];
            dp[(i + 1) & 1][t] = min(dp[(i + 1) & 1][t], dp[i & 1][s] + 1);
        }
    }
    cout << dp[(cnt + 1) & 1][(1 << k) - 1] << endl;
    memset(val, 0, sizeof(int) * (n + 1));
    for (int i = 1; i <= n; i++)
        g[i].clear();
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

F.Sasha and the Wedding Binary Search Tree(树)

题意:

萨沙克服了重重困难,终于决定与女友结婚。为此,他需要送她一枚订婚戒指。然而,他的女朋友并不喜欢这种浪漫的举动,她喜欢二叉搜索树 † ^{\dagger} 。于是,萨沙决定送给她这样一棵树。

在程序员婚礼网站上花了大量时间后,他找到了一棵完美的二叉搜索树,树根位于顶点 1 1 1。在这棵树上,顶点 v v v的值等于 v a l v val_v valv

但一段时间后,他忘记了一些顶点的值。为了记住找到的树,萨沙想知道,如果已知所有顶点的值都是 [ 1 , C ] [1,C] [1,C]段中的整数,他可以在网站上找到多少棵二叉搜索树。答案对 998244353 998244353 998244353取模。

† ^{\dagger} 二叉搜索树是一棵有根的二叉树,其中任意顶点 x x x都具有以下性质:顶点 x x x左子树中所有顶点的值(如果存在)都小于或等于顶点 x x x的值,顶点 x x x右子树中所有顶点的值(如果存在)都大于或等于顶点 x x x的值。

分析:

本题直接在树上考虑较为麻烦,但是二叉搜索树有一个性质,他的中序遍历是有序的,所以我们可以求出二叉树的中序遍历。即将树形结构转换为序列结构,把二叉树的中序遍历看成这颗二叉树的序列。然后本题就转换为给定一个序列中若干个位置和序列中每个数的值域,求有多少种不降序列。

考虑两个相邻节点 ( i , j ) (i,j) (i,j),我们把原序列拆成 n n n段来数,最后用乘法原理相乘,对于每一段就是每个数有 p o s [ j ] − p o s [ i ] pos[j]-pos[i] pos[j]pos[i]个数,并且有 j − i + 1 j-i+1 ji+1个位置,用隔板法计数即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 5e5 + 10;
int L[N], R[N], val[N];
int n, k;
vector<int> pos;
LL inv[N];

void dfs(int x) {
    if (x == -1) {
        return;
    }
    dfs(L[x]);
    pos.push_back(val[x]);
    dfs(R[x]);
}

LL ksm(LL a, LL b) {
    LL cnt = 1;
    while (b) {
        if (b & 1) {
            cnt = cnt * a % mod;
        }
        b >>= 1;
        a = a * a % mod;
    }
    return cnt;
}


LL C(int a, int b) {
    LL res = 1;
    for (int i = b, j = a; i >= 1; i--, j--) {
        res = res * j % mod * inv[i] % mod;
    }
    return res;
}

void solve() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> L[i] >> R[i] >> val[i];
    }
    pos.clear();
    pos.push_back(1);
    LL ans = 1;
    dfs(1);
    pos.push_back(k);
    for (int i = 0; i < pos.size(); i++) {
        if (pos[i] == -1) {
            int l, r;
            l = r = i;
            while (pos[r] == -1) {
                r++;
                i++;
            }
            int d = r - l;
            ans = (ans * C(pos[r] - pos[l - 1] + d, d)) % mod;
        }
    }
    cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);
    for (int i = N - 1; i >= 1; i--) {
        inv[i] = ksm(i, mod - 2);
    }
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值