알고리즘

20/04/08 USACO 유사코 1월 실버

openingsound 2020. 4. 8. 17:59

실버가 3문제인데 2문제가 이분 탐색이었다. 이분 탐색을 참 좋아하는 거 같다.

Berry Picking, 18319

https://www.acmicpc.net/problem/18319

 

18319번: Berry Picking

Bessie and her little sister Elsie are picking berries in Farmer John's berry patch. Farmer John's patch has exactly $N$ berry trees ($1\le N\le 1000$); tree $i$ contains exactly $B_i$ berries ($1\le B_i\le 1000$). Bessie has exactly $K$ baskets ($1 \le K

www.acmicpc.net

$N$개의 나무에 $(1 \le N \le 1000)$ 열매들이 $B_i$개씩 열려 있다. $(1\le B\le 1000)$

$K$개의 바구니에 베리를 따 넣을 건데 하나의 바구니에는 한 나무에서 나온 베리들만 담을 수 있다.$(1\le K\le1000)$

(한 나무에서 나온 베리들을 여러 바구니에 나눠 담는 건 가능)

짝수개인 $K$개의 바구니 중 열매가 많이 담긴 $K/2$개의 바구니는 동생에게 줘야 한다. 이렇게 주고 남은 베리의 수를 최대로 하여라

범위가 1000이니 완전탐색을 하였다. 

14줄부터 나오는 i는 최대로 딸 수 있는 열매의 개수이다. 모든 나무에서 i개씩 열매를 따고 남는 것들은 우선순위 큐 v에 넣어준다. i개씩 딴 횟수를 ge라고 할 때 ge $\ge$ K이면 내가 가질 수 있는 최고의 열매수는 $i * \frac{K}{2}$개 이고 

ge $\ge \frac{K}{2}$ 이면 ge $- \frac{K}{2}$개 와 우선순위 큐 v상위 $K - $ge개의 합이 정답이 된다. 

ge $\lt \frac{K}{2}$의 경우는 해당 i보다 작은 값에서 얻은 정답을 얻을 수 있다.

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
 
vector<int> tree;
int ans = 0;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        int tem;
        cin >> tem;
        tree.push_back(tem);
    }
    for (int i = 1; i <= 1000; i++) {
        priority_queue<int> v;
        int ge = 0;
        for (int j = 0; j < n; j++) {
            ge += tree[j] / i;
            v.push(tree[j] - tree[j] / i * i);
            if (ge > k)break;
        }
        
        if (ge >= k) {
            ans = max(ans, i * k / 2);
        }
        else if (ge >= k / 2) {
            int tem = 0;
            for (int j = 0; j < k - ge; j++) {
                tem += v.top();
                v.pop();
            }
            ans = max(ans, i * (ge - k / 2+ tem);
        }
    }
    cout << ans;
}
 
 

 

Loan Repayment, 18320

https://www.acmicpc.net/problem/18320

 

18320번: Loan Repayment

Farmer John owes Bessie $N$ gallons of milk ($1\le N\le 10^{12}$). He has to give her the milk within $K$ days. However, he doesn't want to give the milk away too quickly. On the other hand, he has to make forward progress on the loan, so he must give Bess

www.acmicpc.net

 

$K$일 동안 $N$개의 우유를 갚아야 한다. $(1 \le N \le 10^{12})$

단 최소 $M$씩은 갚아야 한다. $(1 \le M \le 10^{12})$ 

양수 $X$를 정해 갚을 껀데  $(1 \le X \le 10^{12})$ 갚아야 될 남은 우유를 $X$로 나눠 그 몫을 갚는다.

당연히 $M$이하면 M을 갚아야 한다. 이 때 $K$일이 될 때 딱 다 갚고 싶어 한다. 

$X$의 최대값을 찾는 문제이다. 

 

아주 간단한 이분탐색 문제처럼 보이고 생각 없이 휘리릭 쳐버리면 TL이 난다...

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
ll fun(ll a) {
    ll cou = 0;
    ll tN = N;
    while (tN) {
        cou++;
        ll tem =tN / a;
        if (tem < M) {
            tem = M;
        }
        tN -= tem;
        if (tN < 0)
            break;
    }
    return cou > K;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> N >> K >> M;
    ll l = 1,r=1000000000000;
    while (l <= r) {
        ll mid = (l + r) / 2;
        if (fun(mid))
            r = mid - 1;
        else
            l = mid + 1;
    }
    cout << r;
}
 

fun 부분에서 시간초과 발생 

 

해당 문제를 고치기 위해선 중복되는 경우를 수식으로 풀어야 한다.

해당 내용이 12번째 줄이다. 

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
ll N, K, M;
 
bool fun(ll rest,ll day, ll a) {//rest: 남은 우유, day: 남은 날짜, a: 문제의 X
    ll tem = rest / a;//뺄 길이
    if (tem <= M) {//앞으로 다 M씩 갚아야 함
        return day * M >= rest;
    }
    if (tem * day < rest) //갚아나갈 tem은 증가 하지 않는데 해당조건 만족 못하면 불가능
        return false;
    if (rest <= 0 )//더 갚는건 
        return true;
    ll time = (rest - tem * a) / tem + 1;//나머지에서 뺼길이 만큼 있으면  한번에 빼도 됨 day가 음수면 걸러짐
    return fun(rest - time * tem, day - time, a);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> N >> K >> M;
    ll l = 1,r=K+1;
    while (l <= r) {
        ll mid = (l + r) / 2;
        if (!fun(N,K,mid))
            r = mid - 1;
        else
            l = mid + 1;
    }
    cout << r;
}
 
 

Wormhole Sort, 18321

https://www.acmicpc.net/problem/18321

 

18321번: Wormhole Sort

Farmer John's cows have grown tired of his daily request that they sort themselves before leaving the barn each morning. They have just completed their PhDs in quantum physics, and are ready to speed things up a bit. This morning, as usual, Farmer John's $

www.acmicpc.net

문제를 읽다보면 제대로 해석하고 있는 게 맞는가 생각이 드는 문제

 $N$마리의 소를 $M$개의 웜홀을 사용해 알맞은 자리로 돌아가게 해 주면 되는 문제이다. $(1 \le N, M \le 10^{5})$

이때 사용한 웜홀의 넓이의 최소값이 최대가 되게 해야 한다.

단 윔홀의 넓이 $w_i$의 범위가 $(1 \le w_i \le 10^{9})$ 길어 $w_i$로 이분 탐색을 해줘야 한다.

이분탐색을 해야 한다는 것을 이전 문제보다 쉽게 풀 수 있다. 

단 웜홀의 끝인 $a_i, b_i$의 범위가 N의 범위와 같기 때문에 2차원 배열로 연결을 나타내면 안 되고 vector<pair<int,int>> adj[MAXN] 혹은 vector<Edge*> adj[MAXN];를 사용 해야 한다. 

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int N, M;
int arr[MAXN];
int vis[MAXN];
 
struct Edge {
    int to, c;
    Edge(): Edge(-10) {}
    Edge(int to1, int c1) : to(to1), c(c1) {}
};
vector<Edge*> adj[MAXN];
vector<int> v;
 
void dfs(int node,int worm,int che) {//node: 목장위치 , worm : 웜홀 최소 크기, che: 이번방문 
    vis[node] = che;
    if (node != arr[node])
        v.push_back(arr[node]);
    for (auto A : adj[node]) {
        int there = A->to;
        int siz = A->c;
        if (siz < worm)
            continue;
        if (vis[there] == 0)
            dfs(there, worm, che);
    }
}
bool fun(int a) {//이분탐색시 가능 불가능 판별
    for (int i = 1; i <= N; i++)vis[i] = 0;//초기화 
    for (int i = 1; i <= N; i++) {
        if (vis[i] == 0 && arr[i] != i) {// 방문한 적이 없고 소가 제자리에 있지 않을때
            v.clear();
            dfs(i,a,i);//dfs를 통해 방문한 목장을 다 i로 체크해주고 만난 제자리가 아닌 소들을 v에 넣어준다.
            for (int a : v) {//소들이 돌아가야 할 목장을 이번 dfs때 방문 하지 못했다면 그 소는 돌아 갈 수 없다. 
                if (vis[a] != i)
                    return false;
            }
        }
    }
//모든 소가 돌아갈 수 있다.
    return true;
}
 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> N >> M;
    bool che = 0;
    for (int i = 1; i <= N; i++) {
        cin >> arr[i];
        if (arr[i] != i)
            che = 1;
    }
    for (int i = 1; i <= M; i++) {//간선 입력
        int a, b, c;
        cin >> a >> b >> c;
        Edge* e1 = new Edge(b, c), * e2 = new Edge(a, c);
        adj[a].push_back(e1);
        adj[b].push_back(e2);
    }
    if (che == 0) {//필요가 없을경우 
        cout << -1;
        return 0;
    }
    int l = 1;
    int r = 1000'000'000;
    while (l <= r) {
        int mid = (l + r) / 2;//웜홀의 최소 넓이
        if (!fun(mid))
            r = mid - 1;
        else
            l = mid + 1;
    }
    cout << r;
}
 
 

 

'알고리즘' 카테고리의 다른 글

20/04/15 USACO 2019 December Contest Silver 유사코 실버  (0) 2020.04.15
20/04/12 유사코 2020 2월(USACO 2020 February)  (0) 2020.04.12
20/04/06 유사코  (0) 2020.04.07
20/04/03  (0) 2020.04.03
20/04/02  (0) 2020.04.02