AtCoder Beginner Contest 226

A - Round decimals

将给定的浮点数四舍五入。

\(round(x)\)

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;
 
int main()
{
    double n;
    cin >> n;
    cout << round(n) << "\n";
    return 0;
}

B - Counting Arrays

有 \(n\) 个序列,求不同的序列个数?

\(set\) 去重

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;
 
int n;
set<vector<int> > S;
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    while(n -- )
    {
        int k, x;
        vector<int> v;
        cin >> k;
        while(k -- )
        {
            cin >> x;
            v.push_back(x);
        }
        S.insert(v);
    }
    cout << (int)S.size() << "\n";
    return 0;
}

C - Martial artist

有 \(n\) 个动作需要学,学习动作 \(i\) 需要时间 \(T_i\), 只有学完一个动作才能学另一个,动作之间有依赖关系,求学会动作 \(n\) 所需的最少时间?

建图,能够走到动作 \(n\) 的所有动作都需要学,不妨把 \(n\) 当作起点,遍历所有能到达的点。

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;
 
typedef long long LL;
const int N = 2e5 + 20;
 
int n, w[N];
vector<int> G[N];
bool st[N];
LL ans;
 
void DFS(int u)
{
    st[u] = true;
    ans += w[u];
    for(auto v : G[u])
        if(!st[v])
            DFS(v);
}
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; ++ i)
    {
        cin >> w[i];
        int k, x; 
        cin >> k;
        while(k -- )
        {
            cin >> x;
            G[i].push_back(x);
        }
    }
    DFS(n);
    cout << ans << "\n";
    return 0;
}

D - Teleportation

二维坐标系上有 \(n\) 个点,可以使用一种操作 \((a, b)\) , 从 \((x, y)\) 移动到 \((x + a, y + b)\) 。

若想在任意两个点之间互相到达,所需的最少操作种类数?

令 \(g = gcd(a, b)\), \((a, b)\) 一定可以由 \((\dfrac{a}{g}, \dfrac{b}{g})\) 凑出。

统计任意两点 \(i\) 和 \(j\) 间距离 \((x_i - x_j, y_i - y_j)\), 令 \(g_{i, j} = gcd(|x_i - x_j|, |y_i - y_j|)\),

答案即为 \((\dfrac{x_i - x_j}{g_{i,j}}, \dfrac{y_i - y_j}{g_{i,j}})\) 的不同种类数。

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;
 
const int N = 500 + 10;
 
int n;
int x[N], y[N];
set<pair<int, int> > S;
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);    
    cin >> n;
    for(int i = 1; i <= n; ++ i)
        cin >> x[i] >> y[i];
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
        {
            if(i == j) continue;
            int xx = x[i] - x[j], yy = y[i] - y[j];
            int g = __gcd(xx, yy);
            if(g < 0) g = -g; 
            xx /= g, yy /= g;
            S.insert({xx, yy});
        }
    cout << S.size() << "\n";
    return 0;
}

E - Just one

给定 \(n\) 个点 \(m\) 条边的简单无向图,每一条边可以给定一个方向,共有 \(2^m\) 中方案,统计每个点有且只有一条出边的方案数。

若某个连通分支只有单个环,贡献为 \(2\) , 其他情况均不合法,

问题转化为求只有单个环的联通分支个数 \(t\) , 答案即为 \(2^t\)

如何判断一个连通分支是否是单个环: 若无环,则 \(n\) 个点的分支有 \(n - 1\) 条边;若有一个环,则边数 \(+1\),有 \(n\) 条边;若有多个环,边数 \(\ge n\) ; 故只需要判断该联通分支点数和边数是否相等即可

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;
 
const int N = 2e5 + 20;
const int P = 998244353;
 
int n, m;
vector<int> G[N];
bool st[N];
int point, edge;
 
void DFS(int u)
{
    point ++;
    edge += G[u].size(); 
    st[u] = true;
    for(auto v : G[u])
        if(!st[v])
            DFS(v);
}
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; ++ i)
    {
        int a, b; 
        cin >> a >> b;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    int ans = 1;
    for(int i = 1; i <= n; ++ i)
        if(!st[i])
        {
            point = edge = 0;
            DFS(i);
            if(point * 2 == edge) ans = 2ll * ans % P;
            else ans = 0;
        }
    cout << ans << "\n";
    return 0;
}

F - Score of Permutations

对于一个给定的排列 \(P = (p_1, p_2, ..., p_n)\), 定义 \(S(P)\) 为:

初始时刻,每个人手中有一个球,每经过一轮,第 \(i\) 个人把手上的球传给 \(p_i\), \(S(P)\) 为至少经过一轮,每个人初始的球重新回到各自手上的最少轮数。

令 \(S_n\) 为 \((1, 2, ..., N)\) 的所有排列,求\(\sum\limits_{P\in S_n}S(P)^k \; mod\; 998244353\)

首先考虑对于一个排列 \(P\) , 如何计算它的 \(S(P)\) , 从 \(i\) 向 \(p_i\) 连一条边,可以发现球会在每一个环之间循环传递,假设形成了 \(k\) 个环,环的大小分别为 \((c_1,c_2,..., c_k)\), 当所有环都回到初始状态时, 所需的轮数为 \(lcm(c_1, c_2, ..., c_k)\)

考虑 \(dp\) 计算答案, 令 \(dp[i][j]\) 为给 \(i\) 个人已经分配好在哪个环中,且当前所有环的 \(lcm\) 为 \(j\) 的方案数,转移方程为:

\[dp[i + x][lcm(j, x)] = dp[i][j] * C_{n- i - 1} ^ {x - 1} * (x - 1)! \]

对于上述状态转移方程,我们挑出 \(x\) 个人组成一个新的环,为了保证不会重复,可以给每次选出的环有序化,即每次先从剩下的人里面选择一个编号最小的,然后再从剩余的 \(n - i - 1\) 个人中选出 \(x - 1\) 个组成这个大小为 \(x\) 的环,即 \(C_{n - i - 1}^{x - 1}\)。对于选出的 \(x\) 个数,排列共有 \(x!\) 种, 又因为是环,所以有 \((x - 1)!\) 种方案。

由于 \(lcm\) 过大,状态转移可以用 \(map\) 实现; 初始状态为 \(dp[0][1] = 1\),答案即为 \(\sum dp[n][s] * s^k\)

Sample Code (C++)
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int P = 998244353;
const int N = 50 + 5;

LL n, k, fac[N], C[N][N];
map<LL, LL> dp[N];

LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; }
LL lcm(LL a, LL b) { return a * b / gcd(a, b); }
LL power(LL a, LL b)
{
    LL res = 1;
    for(; b; b >>= 1, a = a * a % P)
        if(b & 1) res = res * a % P;
    return res;
}

int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    fac[0] = 1;
    for(int i = 1; i < N; ++ i) fac[i] = fac[i - 1] * i % P;
    for(int i = 0; i < N; ++ i)
        for(int j = 0; j <= i; ++ j)
        {
            if(!j) C[i][j] = 1;
            else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
        } 
    cin >> n >> k;
    dp[0][1] = 1;
    for(int i = 0; i < n; ++ i)
        for(auto [p, q] : dp[i])
            for(int x = 1; x <= n - i; ++ x)
            {
                LL& d = dp[i + x][lcm(p, x)];
                LL t = dp[i][p] * C[n - i - 1][x - 1] % P * fac[x - 1] % P;
                d = (d + t) % P; 
            }
    LL ans = 0;
    for(auto [p, q] : dp[n])
        ans = (ans + power(p, k) * q % P) % P;
    cout << ans << "\n";
    return 0;    
}
上一篇:零基础java自学流程-Java语言高级226


下一篇:226反转二叉树