문제풀기

AtCoder Beginner Contest 226

lml 2021. 11. 7. 23:42

https://atcoder.jp/contests/abc226/tasks

 

오랜만에 해서 그런가, 아무튼 이번 주 난이도가 꽤 쉬워보임.

 

A. Round decimals

llround(x) 함수를 쓰든

y = (int) x 를 한 다음에 x와 y+0.5와의 크기관계를 보고 올림/내림을 하면 된다.

 

B. Counting Arrays

i) set<vector<int>>를 이용하여 원소의 개수를 답으로 제출하면 됨

ii) vector<vector<int>>에 모두 넣어두고 sort한 다음에 인접한 애들끼리 비교하면 됨

 

C. Martial artist

BFS든 DFS이든 n번 기술을 배우기 전에 필요한 모든 기술을 다 체크하고, 마지막에 시간의 총합을 구하면 됨

 

D. Teleportation

어떤 움직임 (m, n)에 대하여 g = gcd(m, n)이라면 (m/g, n/g)를 배우는 것이 무조건적으로 더 이득이다.

따라서 set<pii>에 모두 넣어두고 원소의 수를 구하면 된다.

(당연히 m과 n 중에 0인 것이 있다면 gcd가 이상해지기 때문에 {0, 1}, {1, 0}, {0, 0}등으로 해야한다.)

 

E. Just one

답지의 코드가 상당히 이쁘다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
 
using namespace std;
 
#define N 200100
#define MOD 998244353
#define ll long long
#define rep(i, n) for(int i = 0; i < n; ++i)
#define pb push_back
 
vector<int>e[N];
bool used[N];
int x, y;
void dfs(int k) {
    used[k] = true;
    x++;
    y += e[k].size();
    rep(i, e[k].size())if (!used[e[k][i]])dfs(e[k][i]);
    return;
}
 
int main(void) {
    int n, m;
 
    ll ans = 1;
    cin >> n >> m;
    rep(i, n)used[i] = false;
    rep(i, m) {
        cin >> x >> y;
        e[x - 1].pb(y - 1);
        e[y - 1].pb(x - 1);
    }
    rep(i, n) {
        if (!used[i]) {
            x = 0;
            y = 0;
            dfs(i);
            if (y == (x * 2))ans = (ans * 2) % MOD;
            else ans = 0;
        }
    }
    cout << ans << endl;
 
    return 0;
}
 
cs

결국 언제 ans = 0이 되느냐가 포인트이다.

어차피 하나의 cycle 비슷한 모양당 방향이 두 가지라서 가능하기만 하다면 2^(cycle 수)이기 때문이다.

답지에서 상당히 자세하게 왜 y = 2x임이 보장되어야하는지는 증명되어 있는 듯하다.

그냥 조금 더 감각적으로 접근한다면 cycle 모양이 되는 곳이 단 하나만 있어야 하기 때문에 v = e가 성립해야한다.

이때 결국 \(y = \sum{e[k].size()}\)가 간선의 수의 두 배이므로 꼭짓점의 수인 두 배와 이것이 같으면 된다.

 

조금 더 감각적이지만 복잡하게 간다면

만일 e[k].size()가 1이라면, 점 k에서 나가는 간선은 반드시 그 간선이 되어야 한다. 이후 이 간선을 없애준다.

이 과정을 반복해서 e[k].size()가 1인 점이 없도록 한다.

이후에는 모든 점들이 오직 순수한 cycle로만 남아야 하기 때문에

모두 e[k].size()가 2가 되고 BFS로 정말 cycle들이 잘 만들어졌는지 확인하며 동시에 그 개수를 세주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
signed main(void) {
    IOS;
 
    int n, m; cin >> n >> m;
    vector<multiset<int>> adj(n);
    for (int i = 0; i < m; i++) {
        int u, v; cin >> u >> v;
        u--, v--;
        adj[u].insert(v);
        adj[v].insert(u);
    }
    if (n != m) { cout << 0return 0; }
    vi check(n);
    bool able = true;
    vi asize(n);
    queue<int> q;
    for (int i = 0; i < n; i++) {
        asize[i] = adj[i].size();
        if (asize[i] == 1) q.push(i);
        if (asize[i] == 0) able = false;
    }
    while (!q.empty()) {
        int x = q.front(); q.pop();
        if (adj[x].size() == 0) {
            able = false;
            break;
        }
        else {
            check[x] = 1;
            adj[*adj[x].begin()].erase(x);
            asize[*adj[x].begin()]--;
            if (asize[*adj[x].begin()] == 1) q.push(*adj[x].begin());
            adj[x].clear();
            asize[x]--;
        }
    }
    int g = 0;
    for (int i = 0; i < n; i++) {
        if (asize[i] != 0 && asize[i] != 2) {
            able = false;
            break;
        }
        if (check[i]) continue;
        g++;
        queue<int> q1; q1.push(i);
        while (!q1.empty()) {
            int x = q1.front(); q1.pop();
            for (auto it = adj[x].begin(); it != adj[x].end(); it = next(it)) {
                if (check[*it]) continue;
                check[*it] = 1;
                q1.push(*it);
            }
        }
    }
    if (!able) { cout << 0return 0; }
    else cout << modpow(2, g);
}
cs

 

F. Score of Permutations

가능한 Score의 개수가 많지 않다는 것을 깨닳으면 map으로 그냥 각 점수당 몇 개의 순열이 존재하는지 세면 된다.

우선 score이 어떻게 만들어지는지 보자. 

어떤 순열 S에 대해 (1, 2, ... n)과 S가 대응되는 함수를 생각하자.  ex) {1, 2} -> {2, 1} f(1) = 2, f(2) = 1

그러면 원소마다 어떤 cycle을 형성하게 될 것이다. Score는 결국 이 cycle들의 크기의 최소공배수이다.

합이 50 이하인 숫자들의 최소공배수가 그렇게 많이 존재하지는 않는다.

그래서 결국 n = 2부터 n = 50까지 그냥 어떤 점수를 가지는 순열의 수가 몇 개 존재하는지 모두 세면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
signed main(void) {
    IOS;
    make_com();
    int n, k; cin >> n >> k;
    vector<map<intint>> m(51);
    m[0][1= 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            for (auto it = m[i - j].begin(); it != m[i - j].end(); it = next(it)) {
                int push = lcm(it->first, j);
                m[i][push] += P(i - 1, j - 1* it->second;
                m[i][push] %= MOD;
            }
        }
    }
    int ans = 0;
    for (auto it = m[n].begin(); it != m[n].end(); it = next(it)) {
        ans += modpow(it->first, k) * it->second;
        ans %= MOD;
    }
    cout << ans;
}
cs

m[i][j]는 길이가 i일 때 점수가 j인 순열의 수를 의미한다.

m[i]에서 m[i+1]로 갈때에는 새롭게 추가되는 원소가 길이 얼마의 cycle을 만들지 생각해서 점화식을 만들었다.

(마지막 원소가 길이 j짜리 cycle을 만든다면 이 cycle을 결정하는게 P(i-1, j-1), 점수는 lcm(기존점수, j))

 

G. The baggage

이거 그냥 솔직히 5라서 겁나 쉽게 풀었고, 머릿속으로 대충 납득도 가서 바로 답을 제출함.

자기 무게와 동일한 가방이 있다면 그것을 드는게 이득이고, 그 이후에는 최대한 제일 무거운 것을 드는게 나음.

5이하이기 때문에 놀랍게도 반례 없음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
signed main(void) {
    IOS;
    int t; cin >> t;
    while (t--) {
        vi a(6), b(6);
        for (int i = 1; i <= 5; i++cin >> a[i]; //parcel
        for (int i = 1; i <= 5; i++cin >> b[i]; //power
        bool able = true;
        for (int i = 1; i <= 5; i++) {
            int plus = min(a[i], b[i]);
            a[i] -= plus;
            b[i] -= plus;
        }
        for (int i = 5; i > 0; i--) {
            if (b[i] < a[i]) able = false;
            for (int j = i; j > 0; j--) {
                int plus = min(b[i], a[j]);
                b[i - j] += plus;
                b[i] -= plus;
                a[j] -= plus;
            }
        }
        if (able) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
 
}
cs