AtCoder Beginner Contest 226(A-H)

本文详细介绍了AtCoder Beginner Contest 226中的题目,涵盖从简单到中等到高级的各类算法问题。包括浮点数四舍五入、数组组合计数、技能树构建、瞬移咒语、无向图定向、排列分数计算、行李分配以及随机变量期望值等。通过实例解析和代码实现,帮助读者深入理解并掌握相关算法技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AtCoder Beginner Contest 226(A-H)

@TOC

知识点整理:

题号知识点备注
A
B
CBFS
D简单模拟、数学
E图论好题,需要记住思路
FDP较为复杂的DP
G贪心ARC的一道类似题
H概率期望,DP

签到题、简单题


A - Round decimals

求浮点数的四舍五入

#include <bits/stdc++.h>

using namespace std;

int main() {
  double x;
  cin >> x;
  cout << (int)(x+0.5) << endl;
  return 0;
}

B - Counting Arrays

给你很多数组,判断有几种不同的数组

直接用set模拟即可

#include <bits/stdc++.h>

using namespace std;

set<vector<int>> s;

int n, l, a;

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        vector<int> arr;
        cin >> l;
        for (int i = 0; i < l; i++) {
            cin >> a;
            arr.push_back(a);
        }
        s.insert(arr);
    }
    cout << s.size() << endl;
    return 0;
}

C - Martial artist

给你 n n n 个技能,点亮某个技能需要一些前置技能,还需要花费 t [ i ] t[i] t[i]的时间,初始技能树是空的,问把技能 N N N点亮至少花费多少时间?

一开始用拓扑排序做错了,后来发现构图然后从N点开始直接bfs即可

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5 + 10;
int t[N];
int k[N];
vector<int> a[N];
int T[N];

int vis[N];
ll res;
int main() {
    queue<int> q;
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &t[i], &k[i]);
        for (int j = 1; j <= k[i]; j++) {
            int x;
            scanf("%d", &x);
            a[i].push_back(x);
        }
    }
    q.push(n);
    vis[n] = 1;
    while (q.size()) {
        int t = q.front();
        q.pop();
        int j;
        for (int i = 0; i < a[t].size(); i++) {
            j = a[t][i];
            if (vis[j])
                continue;
            else {
                vis[j] = 1;
                q.push(j);
            }
        }
    }

    for (int i = 1; i <= n; i++) {
        //cout << vis[i] << endl;
        if (vis[i] == 1) {
            res += t[i];
        }
    }
    cout << res << endl;
}

中等题


D - Teleportation

有一种咒语记为 ( a , b ) (a,b) (a,b),可以从地图上的 ( x , y ) (x,y) (x,y)点瞬移到 ( x + a , y + b ) (x+a, y+b) (x+a,y+b)点。现在在笛卡尔系上有 N N N个点,问至少需要多少种咒语,才可以在这 N N N个点之间任意行走?

这道题出乎意料的简单,看到 N ≤ 500 N \le 500 N500, 说明 O ( n 2 ) O(n^2) O(n2)肯定是可行的。N个点两两做得到 N 2 N^2 N2个差值,每个差值对应两个咒语(可以往返),但是这样不是最优的,因为可以取最大公约数达到最大的复用性,因此再取一个gcd就可以了,注意有0的情况。用set维护,最后答案是set大小再乘2,总的时间复杂度是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll, ll> pii;

const int N = 510;

int n;
ll x[N], y[N];

set<pii> s;

ll gcd(ll x, ll y) {
    return y == 0 ? x : gcd(y, x % y);
}


int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i];
    }
    
    for (int i = 1; i <= n ; i++) {
        for (int j = i+1; j <= n; j++) {
            ll dx = x[i] - x[j], dy = y[i] - y[j];
            ll g = gcd(dx, dy);

            if (g != 0) {
                dx /= g, dy /= g;
            }

            s.insert({dx, dy});
        }
    }

    cout << s.size() * 2 << endl;
    return 0;
}

E - Just one

题意:

给你 n n n m m m边的无向图,问有多少种加边的方案,使得每个点的出度都是1?

题解

一开始我考虑的是跟判环有关系,但是会TLE。

其实看了答案,实现过程并不难:对每个连通分量,只要点数==边数,这个连通分量对答案就有2的贡献,否则就无解。

证明如下:

要想实现 将无向图定向,使得每个点出度均为 1
那么就需要每个点都有一条出边
对于一个连通图来说 如果点数>边数,要么是不连通图,要么就是一颗树,无法实现每个点都有出边,因为点数的边数不对

如果点数<边数的连通图,由于要实现每个点都有一条出边,那么可以先使每个点都出去一条边,此时用了一些边,但是还有一些边没有定方向,由于所有点的定了一个出去的方向,再给摸
个点定一个出边的话,就与题意矛盾

所以只能点数=边数

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 200100;
const int MOD = 998244353;

struct Edge {
    int to, next;
} e[N << 1];

int n, m, head[N], vis[N], idx = 1;

int vert, edge; // 表示点数和边数

void add_edge(int u, int v) {
    e[idx].to = v;
    e[idx].next = head[u];
    head[u] = idx++;
}
void clear_graph() {
    memset(head, -1, sizeof(head));
    memset(e, 0, sizeof(e));
    idx = 1;
}

void dfs(int u) {
	vis[u] = true;
	vert++;
	for (int i = head[u]; ~i; i = e[i].next) {
		edge++;
		int j = e[i].to;
		if (!vis[j]) dfs(j);
	}
}
int main() {
    cin >> n >> m;
	int u, v;
	clear_graph();
    for (int i = 1; i <= m; i++) {
        cin >>u>>v;
		add_edge(u, v), add_edge(v, u);
    }
    
	ll res = 1;
    for (int i = 1; i <= n ; i++) {
        if (!vis[i]) {
			vert = edge = 0;
			dfs(i);
			if (vert*2 == edge) res = res * 2 % MOD;
			else res = 0;
		}
    }

    cout << res << endl;
    return 0;
}


F - Score of Permutations

题意:

定义一个 1 − n 1-n 1n的排列 P P P的分数 S ( P ) S(P) S(P)为:

N N N个人,初始的时候按 1 , 2 , . . . n 1,2,...n 1,2,...n站着,每轮让 i i i走到 p i p_i pi的位置,直到每个人都走回初始位置,走的轮数记为排列 P P P的分数 S ( P ) S(P) S(P)

给出 N , K N,K N,K,求 ∑ P ∈ S N S ( P ) K \sum_{P\in S_N}S(P)^K PSNS(P)K .答案对998244353取模。

题解

这道题可以用DP来做,对于这种排列来回换座位的题,通常用图论的思想来考虑,把 i → p i , p i → p [ p i ] . . . . i \to p_i, p_i \to p[p_i].... ipi,pip[pi]....连边,则构成若干个闭环,每个环走它的长度的时候,回到初始点。

故对于每种排列,其分数为该排列对应所有环大小的LCM,因此可以设计一种DP的关系:

d p [ i ] [ j ] dp[i][j] dp[i][j]表示前i个人,环的LCM为j的方案数,则有

d p [ i + x ] [ l c m ( j , x ) ] = d p [ i ] [ j ] ∗ C n − i − 1 x − 1 ∗ ( x − 1 ) ! dp[i+x][lcm(j, x)]=dp[i][j]*C_{n-i-1}^{x-1}*(x-1)! dp[i+x][lcm(j,x)]=dp[i][j]Cni1x1(x1)!

d p [ i ] [ j ] dp[i][j] dp[i][j]这个状态,考虑下一个环的大小为x,那么下一个环要从剩下的 n − i n-i ni个数里先固定选一个下标最小的,使得每次转移时,每个环内的最小值一定是单调递增的,而长度为 x x x的环的排列数是 ( x − 1 ) ! (x-1)! (x1)!,因此得到上述转移关系。

实现的时候,因为 j j j状态非常大,还可能爆int,而且绝大多数是0,故可以用map<ll, ll>优化,总的时间复杂度应该是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 55, mod = 998244353;
ll n, k;
ll fact[N], infact[N];
map<ll, ll> dp[N];

ll qmi(ll a, ll k) {
    ll res = 1;
    while (k) {
        if (k & 1) res = (ll)res * a % mod;
        k >>= 1;
        a = a * a%mod;
    }
    return res;
}

void init() {
    fact[0]=infact[0]=1;
    for(int i=1;i<N;i++) 
    {
        fact[i]=(ll)fact[i-1]*i%mod; 
        infact[i]=(ll)infact[i-1]*qmi(i,mod-2)%mod; 
    }
}

ll C(ll a, ll b) {
    if (a < b) return 0;
    return fact[a] * infact[b] % mod * infact[a - b] % mod;
}

ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }



int main() {
    init();
    cin >> n >> k;
    dp[0][1] = 1;
    for (int i = 0; i < n; i++) {
        for (auto it : dp[i]) {
            for (int j = 1; i + j <= n; j++) {
                dp[i + j][lcm(j, it.first)] = (dp[i + j][lcm(j, it.first)] + it.second * C(n - i - 1, j - 1) %mod * fact[j - 1] % mod) % mod;
            }
        }
    }
    ll res = 0;
    for (auto it : dp[n]) {
        res = (res + it.second * qmi(it.first, k)) % mod;
    }
    cout << res << endl;
}


高级题

G - The baggage

题意:

给你五种物品,质量分别是1,2,3,4,5,个数分别是 A 1 , A 2 , A 3 , A 4 , A 5 A_1,A_2,A_3,A_4,A_5 A1,A2,A3,A4,A5

现有五种工人,负重分别是1,2,3,4,5,个数分别是 B 1 , B 2 , B 3 , B 4 , B 5 B_1,B_2,B_3,B_4,B_5 B1,B2,B3,B4,B5

问这些工人能否带走这些物品?

题解

跟这道题A - Make 10很像,看数据范围就知道没法遍历,只能设计一种贪心思路。

从最重的物品开始贪心:

  • 负重5的工人搬质量5
  • 负重4的工人搬质量4
  • 负重5的工人搬质量4
  • 负重3的工人搬质量3
  • 负重5的工人搬质量3
  • 负重4的工人搬质量3
  • 负重2的工人搬质量2
  • 负重5,4,3的工人搬质量2
  • 。。。

这个策略的证明就不说了,实现上述过程即可,如果还剩物品没搬走,则输出No

每个样例都是 O ( 1 ) O(1) O(1)

#include <bits/stdc++.h>
using namespace std;

#define N 200010
#define MOD (ll)998244353
#define ll long long
#define rep(i, n) for(int i = 0; i < n; ++i)

ll a[6];
ll b[6];

void pack(ll x, ll y) {
	ll c = min(a[x], b[y]);
	a[x] -= c;
	b[y] -= c;
	b[y - x] += c; // c个工人的负重还剩下y-x
	return;
}

int main(void) {
	ll t;
	bool ans;
	cin >> t;
	rep(tt, t) {
		rep(i, 5)cin >> a[i + 1];
		rep(i, 5)cin >> b[i + 1];
		a[0] = 0;
		b[0] = 0;

		pack(5, 5);
		pack(4, 4);
		pack(4, 5);
		pack(3, 3);
		pack(3, 5);
		pack(3, 4);
		rep(i, 4)pack(2, 5 - i);
		rep(i, 5)pack(1, 5 - i);

		ans = true;
		rep(i, 5)if (a[i + 1] > 0)ans = false;
		if (ans)cout << "Yes" << endl;
		else cout << "No" << endl;
	}

	return 0;
}

H - Random Kth Max

题意:

给你 N N N连续随机变量 X 1 , X 2 . . . X n X_1,X_2...X_n X1,X2...Xn.

其中 X i X_i Xi 在区间 [ L i , R i ] [L_i,R_i] [Li,Ri]内均匀分布,求第 K K K大数的期望。

题解

一、求解子问题

先看这个子问题:

N N N连续随机变量 X 1 , X 2 . . . X n X_1,X_2...X_n X1,X2...Xn都服从 U ( 0 , 1 ) U(0, 1) U(0,1),求第 k k k大的数的期望。

我们在大学的**《概率论与数理统计》**学过,均匀分布的概率分布函数 F ( x ) = x F(x)=x F(x)=x.对于整个问题,其概率分布函数 F X ( x ) = P ( X < x ) F_X(x)=P(X<x) FX(x)=P(X<x), 也就是第 K K K大的数小于 x x x的概率。

这个期望很难用正常算法解出来,所以官方题解给出了另一种反向思路:

我们算 1 − F ( x ) 1-F(x) 1F(x), 也就是第 K K K大的数大于 x x x的概率。

这个事件可以分为以下情况:(等于 x x x归于任何一种情况都行)

  • K K K个数大于 x x x, N − K N-K NK个数小于 x x x;
  • K + 1 K+1 K+1个数大于 x x x N − K − 1 N-K-1 NK1个数小于 x x x;

。。。。。

  • N N N个数全大于 x x x.

所以,可以写出:
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ 1-F(x)=\sum_{i…
我们学过,连续随机变量的期望可以用以下公式计算:
E ( X ) = ∫ − ∞ + ∞ x f ( x ) d x (2) E(X)=\int_{-\infty}^{+\infty}xf(x)dx \tag{2} E(X)=+xf(x)dx(2)
其中 f ( x ) f(x) f(x)为概率密度函数。我们可以对上述式子使用分部积分变形:
∫ x f ( x ) d x = ∫ x d F ( x ) = x F ( x ) − ∫ F ( x ) d x (3) \int xf(x)dx = \int xdF(x)=xF(x)-\int F(x)dx \tag{3} xf(x)dx=xdF(x)=xF(x)F(x)dx(3)
X X X的取值范围为 [ a , b ] [a,b] [a,b],带入上式子:
b F ( b ) − a F ( a ) − ∫ a b F ( x ) d x = b − ∫ a b F ( x ) d x = a + ( b − a ) − ∫ a b F ( x ) d x = a + ∫ a b ( 1 − F ( x ) ) d x (4) bF(b)-aF(a)-\int_a^bF(x)dx =b-\int_a^bF(x)dx \\ =a+(b-a)-\int_a^bF(x)dx \\ =a+\int_a^b(1-F(x))dx \tag{4} bF(b)aF(a)abF(x)dx=babF(x)dx=a+(ba)abF(x)dx=a+ab(1F(x))dx(4)

( 1 ) (1) (1)代入 ( 4 ) (4) (4)
E ( X ) = ∫ 0 1 ( ∑ i = k n C n i ( 1 − x ) i x ( n − i ) ) d x = ∑ i = k n C n i ∫ 0 1 ( 1 − x ) i x ( n − i ) d x (5) E(X) = \int_0^1(\sum_{i=k}^nC_{n}^i(1-x)^ix^{(n-i)})dx \\ = \sum_{i=k}^nC_{n}^i\int_0^1(1-x)^ix^{(n-i)}dx \tag{5} E(X)=01(i=knCni(1x)ix(ni))dx=i=knCni01(1x)ix(ni)dx(5)

根据beta函数 的定义,积分里面那东西等于 B ( n − i + 1 , i + 1 ) B(n-i+1,i+1) B(ni+1,i+1), 又根据beta函数和gamma函数的关系,有
B ( P , Q ) = Γ ( P ) Γ ( Q ) Γ ( P + Q ) (6) B(P, Q) = \frac{\Gamma(P)\Gamma(Q)}{\Gamma(P+Q)} \tag6 B(P,Q)=Γ(P+Q)Γ(P)Γ(Q)(6)
带入 ( 5 ) (5) (5):
E ( X ) = ∑ i = k n C n i ∫ 0 1 ( 1 − x ) i x ( n − i ) d x = ∑ i = k n C n i ( n − i ) ! i ! ( n + 1 ) ! = ∑ i = k n 1 1 + n = n − k + 1 1 + n (7) E(X) = \sum_{i=k}^nC_{n}^i\int_0^1(1-x)^ix^{(n-i)}dx \\ = \sum_{i=k}^nC_{n}^i \frac{(n-i)!i!}{(n+1)!} \\ = \sum_{i=k}^n\frac{1}{1+n} \\ = \frac{n-k+1}{1+n} \tag7 E(X)=i=knCni01(1x)ix(ni)dx=i=knCni(n+1)!(ni)!i!=i=kn1+n1=1+nnk+1(7)
这样我们就把子问题算出来了。

二、利用子问题计算原题

我们可以利用子问题的结论,把原题拆成一个一个线段,先让某个排名的数落在长度为1的线段里,再算线段里面某个名次对整个答案的贡献。

因为只是排名,前面几个数怎么选对后面没关系,因此可以用DP来做:

对每个线段 [ A , A + 1 ] [A, A+1] [A,A+1], d p ( i , j , k ) dp(i,j,k) dp(i,j,k)表示前 i i i个数,有 j j j个数在线段右边, k k k个数在线段里面的概率。

那么计算这个dp的时候,我们通过第i个点的选取位置,决定该从哪个状态转移过来,无外乎三种情况:

  • i i i个点取在 A A A左边,那么仍有 j j j个数在线段右边, k k k个数在线段里面,跟 d p [ i − 1 ] [ j ] [ k ] dp[i-1][j][k] dp[i1][j][k]有关;

  • i i i个点取在线段内部,那么有 j j j个数在线段右边, k − 1 k-1 k1个数在线段里面,跟 d p [ i − 1 ] [ j ] [ k − 1 ] dp[i-1][j][k-1] dp[i1][j][k1]有关;

  • i i i个点取在 A + 1 A+1 A+1右边,那么有 j − 1 j-1 j1个数在线段右边, k k k个数在线段里面,跟 d p [ i − 1 ] [ j − 1 ] [ k ] dp[i-1][j-1][k] dp[i1][j1][k]有关;

实现的时候还需要判断下能不能取到,和数组越不越界。

然后算最终答案,第一维肯定是 n n n不用说了,分别讨论每个线段,每个 ( j , s ) (j,s) (j,s)对答案的贡献:

首先,出现在线段 [ a , a + 1 ] [a, a+1] [a,a+1] 中有 s s s个数,右边有 j j j个数的概率是 d p [ n ] [ j ] [ s ] dp[n][j][s] dp[n][j][s], 我们要计算这种概率下,整体第 K K K大的期望,那么就等价于线段里面的第 K − j K-j Kj大的期望,利用第一部分中已有结论,这个期望值就是 1 + k − j s + 1 1+\frac{k-j}{s+1} 1+s+1kj, 线段是从a开始的,还要加上偏移量a,所以答案等于:
∑ a = 0 m a x ( R i ) ∑ ( j , s ) d p [ n ] [ j ] [ s ] ∗ ( a + 1 + k − j s + 1 ) \sum_{a=0}^{max(R_i)}\sum_{(j, s)}dp[n][j][s]*(a+1+\frac{k-j}{s+1}) a=0max(Ri)(j,s)dp[n][j][s](a+1+s+1kj)
最终的时间复杂度约为 O ( n 4 ) O(n^4) O(n4),n只有100,可过。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 110;
const int MOD = 998244353;

int n, K;
ll dp[N][N][N], inv[N];
ll l[N], r[N];


ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

void init() {
    for (int i = 1; i < 110; i++) {
        inv[i] = qpow(i, MOD - 2);
    }
}
int main() {
    cin >> n >> K;
    ll minl = 200, maxr = -1;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
        minl = min(minl, l[i]);
        maxr = max(maxr, r[i]);
    }

    init();
    ll res = 0;
    for (ll A = minl; A <= maxr; A++) {
        memset(dp, 0, sizeof(dp));
        dp[0][0][0] = 1; // 没有数,那么只有0 0 一种情况

        for (int i = 1; i <= n; i++) {

            for (int j = 0; j < K; j++) {
                for (int k = 0; k <= i; k++) {
                    // cout << k << ',' << i << endl;
                    ll& x = dp[i][j][k];
                    // case 1: 左边
                    if (A >= l[i]) { // 能选得到才有意义
                        ll temp = dp[i - 1][j][k] * (min(r[i], A) - l[i]) % MOD;
                        temp = temp * inv[r[i] - l[i]] % MOD;
                        x = (x + temp) % MOD;
                    }

                    // case2
                    if (l[i] <= A && A + 1 <= r[i] && k >= 1) {
                        ll temp = dp[i - 1][j][k - 1] * inv[r[i] - l[i]] % MOD;
                        x = (x + temp) % MOD;
                    }

                    // case 3:
                    if (A + 1 <= r[i] && j >= 1) {
                        ll temp = dp[i - 1][j - 1][k] * (r[i] - max(l[i], A + 1)) % MOD;
                        temp = temp * inv[r[i] - l[i]] % MOD;
                        x = (x + temp) % MOD;
                    }
                }
            }

            for (int j = 0; j < K; j++)
                for (int s = 0; s <= n; s++)
                    if (K - j <= s) {
                        ll aa = dp[n][j][s];
                        ll temp1 = (K-j)*inv[s+1] % MOD;
                        ll temp2 = (A+1+MOD-temp1) % MOD;
                        res += aa * temp2 % MOD;
                        res %= MOD;
                    }

        }
    }

    cout << res << endl;
    return 0;
}
### AtCoder Beginner Contest 388 Problems and Solutions #### Problem A: Sample Problem Title In this problem, contestants are asked to solve a basic algorithmic challenge that tests fundamental coding skills. The task involves processing input data according to specific rules outlined in the contest guidelines. For instance, consider an example where participants need to determine whether a given string meets certain criteria: ```python def check_string(s): if s.startswith('A') and s.endswith('Z'): return "Yes" else: return "No" print(check_string("ABC")) # Output should be based on provided conditions. ``` The solution approach focuses on understanding the requirements clearly before implementing any logic[^1]. #### Problem B: Another Example Task This section would detail another type of question from ABC 388 which might involve more complex algorithms or data structures compared to Problem A. It could cover topics like sorting arrays, searching within lists, etc., depending upon what was actually featured during the event. An illustrative code snippet demonstrating how one may tackle such issues can look as follows: ```cpp #include <bits/stdc++.h> using namespace std; int main() { vector<int> numbers = { /* Input values */ }; sort(numbers.begin(), numbers.end()); // Further operations with sorted list... } ``` Contestants must carefully read through all instructions while attempting these types of questions. #### General Tips for Participating in Contests Like This One Preparing adequately prior to participating includes practicing similar past papers extensively along with reviewing relevant materials related to common themes encountered frequently across various contests hosted by platforms like AtCoder. Additionally, staying updated about recent changes made either concerning submission formats or evaluation methods ensures smoother participation without unexpected surprises arising midway into solving tasks at hand[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值