https://boj.ma/9502/t

 

9502번: Bones’s Battery

 

boj.ma

 

모든 학교 간 이동을 가능하게 하는 최소 배터리 용량을 찾고싶은 문제입니다.

충전은 최대 K번 할 수 있고 시작시 용량이 0이기 때문에 시작할때 반드시 한 번 충전을 해야합니다.

 

용량을 최소한으로 소모하여 이동하는게 이득이기 때문에 어떤 두 정점간 최단경로로 이동하는것이 최적입니다.

 

문제에서 원하는 값은 "최대 K번 충전하여 모든 학교간 이동을 가능하게 하는 충전량의 최소값" 입니다.

완탐을 떠올려 보면

1회 충전 시 충전량이 1일 때 최대 K번 충전해서 모든 학교간 이동 가능하냐?

1회 충전 시 충전량이 2일 때 최대 K번 충전해서 모든 학교간 이동 가능하냐?

1회 충전 시 충전량이 3일 때 최대 K번 충전해서 모든 학교간 이동 가능하냐?

1회 충전 시 충전량이 4일 때 최대 K번 충전해서 모든 학교간 이동 가능하냐?

...

1회 충전 시 충전량이 X일 때 최대 K번 충전해서 모든 학교간 이동 가능하냐?

처럼 결정 문제로 바꿀 수 있고, 위 결정문제에 대한 정답은 단조성을 보이기 때문에 즉, 특정 용량 이후로는 항상 가능하다는 대답이 나오기 때문에 이분탐색으로 최적화가 가능합니다.

 

모든 정점쌍 간 최단경로를 구해야하고, N이 작기 때문에 플로이드 워셜로 최단경로를 구할 수 있습니다.

 

시간복잡도는 이분탐색으로 가능한 구간에 매 탐색마다 플로이드를 돌리기 때문에

$O(N^3 log_210^{11})$ 이 됩니다.

 

코드 (C++)

#include <bits/stdc++.h>
#include <random>
#include <unordered_map>
#include <unordered_set>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#define endl "\n"
#define NM 15*15+10
#define MAX 150010
#define BIAS 1048576
#define X first
#define Y second
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL)
using namespace std;
using ll = long long;
using ull = unsigned long long;

int dx[8] = { 1, 0, -1, 0, 1, 1, -1, -1 };
int dy[8] = { 0, 1, 0, -1, 1, -1, 1, -1 };


int main() {
	fastio;

	int t;
	cin >> t;
	while (t--) {
		int n, K, m;
		cin >> n >> K >> m;
		vector<vector<ll>> dist(n, vector<ll>(n, LINF));

		for (int i = 0; i < n; i++) dist[i][i] = 0;

		for (int i = 0; i < m; i++) {
			ll a, b, c;
			cin >> a >> b >> c;
			dist[a][b] = min(dist[a][b], c);
			dist[b][a] = min(dist[b][a], c);
		}

		for (int k = 0; k < n;k++) {
			for (int i = 0; i < n; i++){
				for (int j = 0; j < n; j++) {
					dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
				}
			}
		}

		auto ok = [&](ll x)->bool {
			vector<vector<ll>> tmp(n, vector<ll>(n, INF));
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < n;j++) {
					if (i == j) tmp[i][j] = 0;
					else if (dist[i][j] <= x) tmp[i][j] = 1;
				}
			}
			for (int k = 0; k < n;k++) {
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < n; j++) {
						tmp[i][j] = min(tmp[i][j], tmp[i][k] + tmp[k][j]);
					}
				}
			}

			for (int i = 0; i < n; i++) {
				for (int j = 0; j < n; j++) {
					if (tmp[i][j] > K)  return false;
				}
			}

			return true;
		};

		ll l = 0, r = LINF;
		ll ans = r;

		while (l <= r) {
			ll mid = l + r >> 1;
			if (ok(mid)) {
				ans = mid;
				r = mid - 1;
			}
			else {
				l = mid + 1;
			}
		}
		cout << ans << endl;

	}
	

	return 0;
}

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 33543 둘이 한 팀  (0) 2025.03.03
백준 2001 보석 줍기  (2) 2024.08.31
백준 17943 도미노 예측  (0) 2024.07.18
백준 7346 유전자 함수  (0) 2024.05.16
백준 23889 돌 굴러가유  (0) 2024.05.16

https://boj.ma/33543/t

 

33543번: 둘이 한 팀

 

boj.ma

문제 요약은

\[
\sum_{i=1}^{N} \max\Bigl( A_i + \text{A증가량}, \; B_i + \text{B증가량} \Bigr)
\]
를 쿼리마다 구하는 문제입니다.

 

당연히 일일히 업데이트 치고, 매번 합을 구하게 되면 시간초과입니다.

$\max(x, y)$ 를 생각해봅시다. $\max(x, y)$ 는  $\max(x, y) = y + \max(x - y, 0)$ 로 나타낼 수 있습니다.

따라서 우리가 구하려는 $ \max( A_i + \text{A증가량}, \; B_i + \text{B증가량})$ 를 바꾸면,

$\max(A_i + \text{A증가량}, B_i + \text{B증가량}) = B_i + \text{B증가량} + \max((A_i + \text{A증가량}) - (B_i + \text{B증가량}), 0)$ 가 됩니다.

따라서 원래 수식을 아래와 같이 나타낼 수 있습니다.

\[\sum_{i=1}^{N} (B_i + \text{B증가량}) + \sum_{i=1}^{N} \max((A_i - B_i) + \text{A증가량 - B증가량}, 0)\]

 

앞쪽 식은 초기 B배열의 전체 합과 쿼리마다 B를 업데이트 칠 때마다 B증가량에 더해주면 $O(1)$에 구할 수 있습니다.

뒤쪽 식은 $(A_i - B_i)$ 를 미리 계산해둔 배열을 만들어두고, 정렬후 누적합 배열을 만들어 줍니다.

이후 각 쿼리마다 A증가량 - B증가량은 $O(1)$ 에 업데이트 하고, $ \sum_{i=1}^{N} \max((A_i - B_i) + \text{A증가량 - B증가량}, 0) $ 을 이분탐색으로 빠르게 구해줍니다.

 

$ A_i - B_i $ 배열을 정렬하고 누적합을 구해놓는다고 했습니다. 순서가 원래 배열의 순서에서 깨지더라도 상관없습니다.

합은 어차피 $A_i - B_i + \text{A증가량 - B증가량}$ 이 0보다 큰것들만 더해지기 때문에 0보다 큰게 몇개인지만 관심가지면 됩니다.

 

 

따라서  $(A_i - B_i) + \text{A증가량 - B증가량}  \geq 0$ 가 되는 $i$를 이분탐색으로 빠르게 찾고, 

$ N - i $ 개 원소에 대해 $ A_i - B_i + \text{A증가량 - B증가량} $ 의 합은  

$$ \text{psum}[N] - \text{psum}[\text{idx}] + \text{(A증가량 - B증가량)}  \times (N - \text{idx}) $$  

으로 나타낼 수 있습니다.

최종 수식은 아래와 같습니다.

\[
\text{sumB} + n \times \text{B증가량} + \left[ (\text{ psum }[n] - \text{ psum }[\text{idx}]) + \text{ (A증가량 - B증가량) } \times (n - \text{idx}) \right]
\]

 

이제 수식대로 구현해주면 됩니다.

 

코드는 생략합니다.

 

 

 $\max(x, y) = y + \max(x - y, 0)$ 아이디어가 중요한것 같습니다.

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 9502 Bones’s Battery  (0) 2025.03.04
백준 2001 보석 줍기  (2) 2024.08.31
백준 17943 도미노 예측  (0) 2024.07.18
백준 7346 유전자 함수  (0) 2024.05.16
백준 23889 돌 굴러가유  (0) 2024.05.16

https://boj.ma/2001/t

 

2001번: 보석 줍기

 

boj.ma

 

그래프가 주어지고 보석이 위치한 노드가 주어집니다

그리고 간선마다 들고 지나갈수 있는 보석의 개수 제한이 있습니다.

 

1번 노드에서 출발해서 다시 1번 노드로 돌아오려고 할 때 가질수 있는 보석의 최대 갯수를 구하는 문제입니다.

 

완전탐색으로 먼저 접근해보면,

1번에서 출발하여 1번으로 돌아오는 모든 경로에 대해 확인하면 답을 구할 수 있지만

방문했던 노드를 또 방문 할 수 있기때문에 많은 경우의 수를 모두 확인해봐야 합니다.

 

방문했던 노드를 다시 방문가능하지만, 굳이 안봐도 되는 경우에 대해서는 거를 수 있지 않을까요?

 

문제의 예제의 그래프입니다.

1, 2, 3, 4, 5번 노드에 보석이 있고 가중치는 들고 지나갈 수 있는 보석의 개수입니다.

 

만약 모든 노드에서 보석을 줍지 않는다고 한다면, 영원히 그래프를 지나다닐 수 있을거에요.

그래서 어떤 노드에 대해서, 내가 1번 노드에서 보석을 주운 상태로 여기를 와본적이 있다면, 그 노드는 굳이 다시 안가봐도 된다는 거죠

 

마찬가지로, 1번 노드, 3번 노드, 5번 노드에서 보석을 주운 상태로 어떤 다른 노드로 가려고 하는데,

1, 3, 5번 노드에서 보석을 주운 상태로 다음 가려는 노드를 가본적이 있다면 안가봐도 되는 것입니다.

 

이제 그래프 탐색을 진행하면서 상태에 대해 잘 관리해주면 됩니다.

하지만 단순히 보석의 개수만으로 상태를 관리하면 올바르게 탐색이 안됩니다.

보석마다 번호를 매겨서 몇번째 보석을 갖고있는지까지 알고있어야 이미 보석을 주운 노드에 방문하더라도

또 보석을 줍는 일을 막을 수 있습니다.

 

${K <= 14}$ 이기 때문에 비트마스킹을 이용해 보석 보유 상태를 관리했습니다.

사실 문제 읽자마자 K <= 14 인거 보고 비트마스킹으로 풀어야겠다고 생각했습니다

 

그래서 visited[a][b] := a번 노드에 b상태로 와본적 있는지

라고 정의해서 bfs를 돌려주면 끝입니다.

 

 

#include <bits/stdc++.h>
#define endl "\n"
#define NM 202020
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 0x3f3f3f3f
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//using ll = int64_t;
//using ull = uint64_t;
using namespace std;
int dx[4] = { 1, 0, -1, 0 };
int dy[4] = { 0, 1, 0, -1 };



int main() {
    fastio;
    
    int n, m, k;
    cin >> n >> m >> k;
    vector<int> exist(n + 1, 0);
    for (int i = 1 ; i <= k; i++){
        int x;
        cin >> x;
        exist[x] = i;
    }
    
    vector<vector<pii>> graph(n + 1);
    
    // visited[a][b] := a섬에 b상태로 와봤는지
    vector<vector<int>> visited(n + 1, vector<int>(1 <<14, 0));
    
    for (int i = 0 ; i < m; i++){
        int a, b, c;
        cin >> a >> b >> c;
        graph[a].push_back({b, c});
        graph[b].push_back({a, c});
    }
    
    // 이진수에서 1의 개수
    auto pop_count = [&](int x)->int{
        int ret = 0;
        while(x){
            if (x&1) ret++;
            x >>= 1;
        }
        return ret;
    };
    
    
    queue<pii> q;
    visited[1][0] = 1;
    q.push({1, 0});
    if (exist[1]){ // 1번 노드에 보석 존재하는 경우
        q.push({1, 1 << exist[1]});
        visited[1][1 << exist[1]] = 1;
    }
    
    while(!q.empty()){
        auto [cur, state] = q.front(); q.pop();
        int cnt = pop_count(state);
        for (auto [nxt, limit] : graph[cur]){
            // 못지나감
            if (cnt > limit) continue;
            
            // 그냥 가기
            if (!visited[nxt][state]){
                visited[nxt][state] = 1;
                q.push({nxt, state});
            }
            
            if (exist[nxt]){ // 다음 위치가 보석이 있는곳이고
                if (!(state & (1 << exist[nxt]))){ // 지금 안주운 상태
                    if (!visited[nxt][state | (1 << exist[nxt])]){ // 현재 상태에서 주워본적도 없음
                        visited[nxt][state | (1 << exist[nxt])] = 1;
                        q.push({nxt, state | (1 << exist[nxt])});
                    }
                }
            }
        }
    }
    
    int ans = 0;
    
    for (int i = 0 ;i < (1 << 14); i++){
        if (visited[1][i]){ // 1번에 i상태로 온적 있다.
            ans = max(ans, pop_count(i));
        }
    }
    cout << ans << endl;
    
    
    return 0;
}

 

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 9502 Bones’s Battery  (0) 2025.03.04
백준 33543 둘이 한 팀  (0) 2025.03.03
백준 17943 도미노 예측  (0) 2024.07.18
백준 7346 유전자 함수  (0) 2024.05.16
백준 23889 돌 굴러가유  (0) 2024.05.16

https://boj.ma/17943/t

 

17943번: 도미노 예측

 

boj.ma

 

${N}$과 ${Q}$, 그리고 ${N-1}$개의 수가 주어집니다.

${N - 1}$개의 수는

${a_0}$ ${\oplus}$ ${a_1}$, ${a_1}$ ${\oplus}$ ${a_2}$, .... , ${a_{n-1}}$  ${\oplus}$ ${a_n}$ 으로, 입력으로 주어진 i번째 숫자는 원본배열의 i번째 수와 i+1번째 수를 xor한 값입니다.

 

이후 ${Q}$개의 쿼리가 주어지는데, 쿼리의 종류가 두 가지 있습니다.

0 x y 는 x번째 수와 y번째 수를 xor한 값을 출력하는 쿼리이고,

1 x y d는 x번째 수가 d일때, y번째 수를 출력하라는 쿼리입니다.

 

xor문제에서는 항상 xor의 중요한 특성 두 가지를 떠올리면 풀리는 경우가 많습니다.

 

${0}$ ${\oplus}$ ${X}$ = ${X}$

 

${X}$ ${\oplus}$ ${X}$ = ${0}$

 

이 두가지 xor연산의 특징을 바탕으로 생각을 해봅니다.

 

0번 쿼리인 x번째 수와 y번째 수를 xor한 값의 결과는

주어진 배열에서

${A_x}$ ${\oplus}$ ${A_{x+1}}$ ${\oplus}$ ${A_{x+1}}$ ${\oplus}$ ${A_{x+2}}$  ${\oplus}$ ${...}$ ${\oplus}$ ${A_{y-2}}$ ${\oplus}$ ${A_{y-1}}$ ${\oplus}$  ${A_{y-1}}$ ${\oplus}$ ${A_y}$ 를 해주면 중간 항이 모두 소거되기 때문에

${A_x}$ ${\oplus}$ ${A_y}$ 와 같습니다.

 

따라서 입력으로 주어진 배열에서 x번째 수부터 y - 1번째 수까지 xor한 값을 출력해주면 됩니다.

 

1번 쿼리는

 

${X}$번째 수가 ${d}$일때 ${Y}$번째 수를 출력하는 것인데, ${A_X}$ ${\oplus}$ ${A_Y}$ ${\oplus}$ ${A_X}$ ${=}$ ${A_Y}$ 이므로

 

0번 쿼리를 그대로 갖고온 결과에 ${d}$ 를 xor 해주면 됩니다.

 

당연히 쿼리마다 일일히 xor해주면 시간초과이므로, 누적합 배열을 이용해 전처리 해주면 쿼리마다 ${O(1)}$ 에 값을 구할 수 있습니다.

 

코드(python)

import sys
input = sys.stdin.readline


n, q = map(int, input().split())
ls = [0] + list(map(int, input().split()))
k = len(ls)
pxor = [0] * (k)
for i in range(1, k):
    pxor[i] = pxor[i- 1] ^ ls[i]
for _ in range(q):
    oper, *a = map(int, input().split())
    if oper:
        x, y, d = a
        print(pxor[y - 1] ^ pxor[x - 1] ^ d)
    else:
        x, y = a
        print(pxor[y - 1] ^ pxor[x - 1])

 

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 33543 둘이 한 팀  (0) 2025.03.03
백준 2001 보석 줍기  (2) 2024.08.31
백준 7346 유전자 함수  (0) 2024.05.16
백준 23889 돌 굴러가유  (0) 2024.05.16
백준 31786 최고의 친구  (0) 2024.05.09

https://boj.ma/7346/t

 

7346번: 유전자 함수

 

boj.ma

 

주어지는 두 유전자 문자열의 길이를 같게 적절히 공백을 추가했을 때 얻어지는 결괏값의 최대치를 알고 싶습니다.

또한 같은 위치에 두 문자열 모두에 공백이 있으면 안 됩니다.

주어지는 문자열의 길이는 100을 넘지 않습니다.

 

완전탐색부터 생각해 보면

문자열 두 개를 만들건대 문자열 각각의 인덱스를 n, m이라고 하면 첫 번째 문자열의 n번째 위치에 공백을 넣거나, 두 번째 문자열의 m번째 위치에 공백을 넣거나, 두 문자열 모두에 공백을 넣지 않거나

3가지 경우에 대해 분기를 나누어 진행하면 답을 찾을 수 있을 것 같습니다.

하지만 모든 경우에 대해 해 본다면 시간초과입니다.

 

예제의 문자열을 그대로 들고 왔습니다.

AGTGATG
GTTAG

 

 

첫 번째 문자열의 3번 인덱스까지 만들었고,  두 번째 문자열의 2번 인덱스까지 만들었을 때 그 이후에 공백을 적절히 채워 최댓값을 찾는 것은 이전에 어떤 방식으로 공백을 채웠든 항상 일정할 것입니다.

따라서 dp로 최적화가 가능합니다.

 

코드(c++)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 100000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    vector<vector<int>> arr = {
            {5, -1, -2, -1, -3},
            {-1, 5, -3, -2, -4},
            {-2, -3, 5, -2, -2},
            {-1, -2, -2, 5, -1},
            {-3, -4, -2, -1, 0}
    };
    string s = "ACGT*";
    map<char, map<char, int>> mp;

    for(int i = 0; i < 5 ;i ++){
        for (int j = 0 ; j < 5; j++){
            mp[s[i]][s[j]] = arr[i][j];
        }
    }

    int t;
    cin >> t;
    while(t--){
        int n, m;
        string s1, s2;
        cin >> n >> s1 >> m >> s2;
        if (n < m) swap(n, m), swap(s1, s2);
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, -1));
        auto recur = [&](const auto&self, int a, int b) -> int{
            if (a == n){
                int total = 0;
                for (int i = b ;i < m; i++){
                    total += mp['*'][s2[i]];
                }
                return total;
            }
            if (b == m){
                int total = 0;
                for (int i = a ;i < n; i++){
                    total += mp[s1[i]]['*'];
                }
                return total;
            }
            int&ret = dp[a][b];
            if (ret != -1) return ret;
            ret = -INF;
            // 두개를 직접 비교
            ret = max(ret, self(self, a + 1, b + 1) + mp[s1[a]][s2[b]]);
            // s1을 공백으로 채운다
            ret = max(ret, self(self, a, b + 1) + mp['*'][s2[b]]);
            // s2를 공백으로 채운다
            ret = max(ret, self(self, a + 1, b) + mp[s1[a]]['*']);
            return ret;
        };

        cout << recur(recur, 0,0 ) << endl;

    }


    return 0;
}

 

약간 귀찮은 전처리만 해주면 됩니다

 

 

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 2001 보석 줍기  (2) 2024.08.31
백준 17943 도미노 예측  (0) 2024.07.18
백준 23889 돌 굴러가유  (0) 2024.05.16
백준 31786 최고의 친구  (0) 2024.05.09
백준 31782 저체온증  (0) 2024.05.02

https://boj.ma/23889/t

 

23889번: 돌 굴러가유

 

boj.ma

 

돌이 굴러가는 위치들이 주어지고, 세울 수 있는 벽의 개수가 주어질 때 벽을 적절한 위치에 세워 파괴되는 모래성의 개수를 최소로 하고 싶습니다.

 

완전탐색부터 생각해 보면

주어지는 N개의 위치에 대해 M개의 벽을 놓는 모든 경우의 수에 대해 해 보는 것입니다.

$_{N} C_{M}$ 가지의 경우에 대해 돌을 놔보고, 그때마다 계산을 해주면? 당연히 시간초과입니다.

 

최적화를 생각해 봅시다

모든 위치에 벽을 놔보는 것이 오래 걸리니 벽을 놓지 않아도 되는 위치에 대해 생각해 봅시다.

 

저는 처음에 주어지는 돌이 굴러가기 시작하는 위치에만 벽을 놓으면 된다고 생각했습니다.

당연하게도 돌이 굴러가다가 막는 것보다 애초에 돌이 구르지 못하게 하는 것이 더 이득이기 때문입니다.

 

하지만 돌의 개수 K 또한 최대 N까지 가능하므로 돌의 위치에만 벽을 세워본다 하더라도 시간초과가 날 것입니다.

 

이제, 벽을 놓지 않아도 되는 돌의 위치에 대해 생각해 봅시다.

 

예를 들어 입력 예제가 다음과 같이

10 1 2
1 2 3 4 5 6 7 8 9 10
1 10

 

이런 형태라면

벽은 하나만 놓을 수 있고 돌이 굴러가기 시작하는 위치는 1, 10이므로

1번 위치에 벽을 세우면 10만큼의 모래성이 파괴되지만 10에 설치하게 되면 45만큼의 모래성이 파괴됩니다.

 

이것을 통해

각 돌마다 벽이 없을 때 파괴하는 모래성의 개수를 알고 있다면, 가장 많이 파괴하는 돌부터 막는 게 최적일 것이라고 생각했습니다.

 

따라서 돌마다 파괴하는 모래성의 개수를 전처리하여 구하고, 정렬하여 낮은 인덱스 순서대로 출력하면 됩니다.

 

코드 (c++)

#include <bits/stdc++.h>

#define endl "\n"
#define NM 100101
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 1000000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//typedef __int128 i128;
using namespace std;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif

    int n, m, k;
    cin >> n >> m >> k;
    vector<int> arr(n + 1), stone(k + 1), psum(n + 1, 0);
    for (int i = 1; i <= n; i++) cin >> arr[i];
    for (int i = 1 ;i <= k; i++) cin >> stone[i];
    for (int i = 1; i <= n; i++) psum[i] = psum[i - 1] + arr[i];
    vector<pii> v;
    for (int i = 1 ; i < k; i++){
        v.push_back({psum[stone[i + 1] - 1] - psum[stone[i] - 1], stone[i]});
    }
    v.push_back({psum[n] - psum[stone.back() - 1], stone.back()});

    auto comp = [&](pii a, pii b) -> bool{
        if (a.X == b.X) return a.Y < b.Y;
        return a.X > b.X;
    };

    sort(all(v), comp);

    vector<int> ans;

    int cnt = min(m, k);
    for (int i = 0 ; i < cnt; i++){
        ans.push_back(v[i].Y);
    }
    sort(all(ans));

    for (auto i : ans) cout << i << endl;

    return 0;
}

 

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 17943 도미노 예측  (0) 2024.07.18
백준 7346 유전자 함수  (0) 2024.05.16
백준 31786 최고의 친구  (0) 2024.05.09
백준 31782 저체온증  (0) 2024.05.02
백준 31778 PPC 만들기  (1) 2024.05.02

https://boj.ma/31786/t 

 

31786번: 최고의 친구

 

boj.ma

 

2가지 쿼리가 주어집니다.

1번 쿼리 : $(x, 0), (-x, 0)$ 에 위치할때 통신 가능한 기지국의 개수 출력

2번 쿼리 : $(x, y)$ 에 기지국이 있었으면 삭제, 없었으면 추가

 

$(x, 0)$ 와 $(-x, 0)$ 이 통신하기 위해서는 어떤 기지국 $(a, b)$ 에 대해 세 점이 이루는 삼각형이 예각 삼각형이어야 합니다.

 

1번 쿼리를 보고 예각 삼각형이 되어야 하므로 당연히 점 $(a, b)$ 의 a가 $ -x < a < x $ 여야 한다는 건 알았습니다.

처음엔 저 사이에만 있으면 무조건 예각삼각형이라고 착각했습니다.

 

중3때 배우는 기하학을 갖고와봅시다

반원에 대한 원주각은 항상 직각입니다.

$(x, 0)$ 와 $(-x, 0)$ 을 지름으로 하는, 반지름의 길이가 x이고 중심이 $(0, 0)$인 원을 생각하면

원안에 있는 모든 점들에 대해서 만들어지는 삼각형은 절대 예각삼각형이 될 수 없습니다.

 

따라서 임의의 기지국 좌표 $(a, b)$에 대해 $ -x < a < x $ 를 만족하면서  $ a^2 + b^2 > x^2$ 인 점들만 답이 될 수 있습니다.

그럼  $ -x < a < x $ 의 개수에서 $ a^2 + b^2 <= x^2$ 인 점의 개수를 빼주면 1번 쿼리를 처리할 수 있습니다.

 

쿼리의 개수가 $3*10^5$개 주어지는 좌표의 범위가 $[-10^9, 10^9]$ 이므로 당연히 완전탐색으로 쿼리를 처리할 수 없습니다.

 

1번쿼리는 특정 범위인 값의 개수, 2번쿼리는 특정 좌표를 추가하거나 삭제하는 쿼리이므로

세그먼트 트리를 생각할 수 있습니다.

그런데, 좌표의 범위가 크기때문에 좌표압축을 해줘야할 것 같은데 온라인으로 좌표가 주어지기때문에 힘들어 보입니다.

 

그래서 쿼리를 미리 다 받아서 나올 수 있는 모든 $x$좌표와 $x^2+y^2$의 값을 미리 계산하여 좌표압축 후에

세그먼트 트리를 적용하면 됩니다.

 

여기까지 생각하고 세그먼트 트리를 통해 구현을 하다가 pbds를 통해서도 충분히 위의 쿼리를 처리할 수 있다고 판단하여 pbds로 구현했습니다.

 

그리고, $x^2 + y^2$의 값들을 관리할때 좌표가 다르더라도 $x^2 + y^2$의 결과는 같을 수 있습니다.

따라서 기지국의 좌표를 따로 관리하는 자료구조(set, map..)도 필요합니다.

 

구현(c++)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 100000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//typedef __int128 i128;
using namespace std;

//pbds -- order_of_key(x) := x미만의 원소의 개수(x가 들어가야할 인덱스 0-based) , find_by_order(k) := (k + 1)번째 원소가 있는 iter
using namespace __gnu_pbds;
// multi-set
using ordered_set_equal = tree<ll, null_type, less_equal<ll>, rb_tree_tag, tree_order_statistics_node_update>;
using ordered_set_equal_pll = tree<pll, null_type, less_equal<pll>, rb_tree_tag, tree_order_statistics_node_update>;
// set
using ordered_set = tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update>;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    ordered_set_equal x_, xy_;
    ordered_set_equal_pll xy_cd;
    // multi-set 원소삭제
    auto pbds_erase = [&](ordered_set_equal &a, ll val)->void{
        auto it = a.find_by_order(a.order_of_key(val));
        if(*it == val) a.erase(it);
    };

    auto pbds_erase_pll = [&](ordered_set_equal_pll &a, pll val) -> void{
        auto it = a.find_by_order(a.order_of_key(val));
        if (*it == val) a.erase(it);
    };

    int q;
    cin >> q;
    while(q--){
        int oper;
        cin >> oper;
        if (oper&1){
            ll x;
            cin >> x;
            // (-x, x)구간의 갯수
            int x_cnt = x_.order_of_key(x) - x_.order_of_key(-x + 1);
            // a^2 + b^2 <= x^2인 (a, b)의 갯수
            int xy_cnt = xy_.order_of_key(x * x + 1);
            cout << x_cnt - xy_cnt<< endl;
        }else{
            ll x, y;
            cin >> x >> y;
            // x^2 + y^2
            ll cur = x * x + y * y;
            auto it = xy_cd.find_by_order(xy_cd.order_of_key({x, y}));
            // 이미 존재하는 기지국
            if (it->first == x && it->second == y) {
                pbds_erase(xy_, cur);
                pbds_erase(x_, x);
                pbds_erase_pll(xy_cd, make_pair(x, y));
            }else{
                xy_.insert(cur);
                x_.insert(x);
                xy_cd.insert(make_pair(x, y));
            }
        }
    }

    return 0;
}

 

 

 

기지국의 좌표를 관리하는건 굳이 pbds를 통해 관리하지 않아도 됩니다.

 

 

 

 

사진 출처 : http://trsketch.dothome.co.kr/a13012

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 7346 유전자 함수  (0) 2024.05.16
백준 23889 돌 굴러가유  (0) 2024.05.16
백준 31782 저체온증  (0) 2024.05.02
백준 31778 PPC 만들기  (1) 2024.05.02
백준 10719 Baekjoon Database Management System  (0) 2024.04.30

https://boj.ma/31782/t 

 

31782번: 저체온증

 

boj.ma

 

문제 조건에 따라 낮동안에 정상체온이 되는 사람들의 그룹은 반드시 직사각형 형태가 됩니다.

 

밤에는 K명이 저체온증이 되는데 만들어지는 직사각형들 중 가로 또는 세로의 길이가 K보다 작거나 같다면 그 변에 있는 모든 사람이 저체온증에 걸리게 하면 낮동안에 정상체온이 될 방법이 없으므로 그 직사각형 그룹은 모두 저체온증이 됩니다.

 

따라서 bfs 한 번으로 정상체온 그룹을 만들어주고,

 

그룹을 만날때마다 가로, 세로 사이즈를 구해서 모두 K보다 크다면 직사각형의 크기만큼을  정답에 추가해주면 됩니다.

총 시간복잡도는 O(NM)입니다.

 

코드(python)

import sys
from collections import deque
input = sys.stdin.readline

def in_range(a, b):
    return 0 <= a < n and 0 <= b < m

n, m, k = map(int, input().split())
board = [list(input().strip()) for _ in range(n)]
q = deque()
visited = [[False] * m for _ in range(n)]
dxy = [(1, 0), (0, 1), (-1, 0), (0, -1)]
for i in range(n):
    for j in range(m):
        if board[i][j] == 'O':
            q.append((i, j))
            visited[i][j] = True

while q:
    x, y = q.popleft()
    board[x][y] = 'O'
    for dx, dy in dxy:
        nx, ny = x + dx, y + dy
        if not in_range(nx, ny):
            continue
        if visited[nx][ny]:
            continue
        if board[nx][ny] == '.':
            cnt = 0
            for ddx, ddy in dxy:
                nnx, nny = nx + ddx, ny + ddy
                if not in_range(nnx, nny):
                    continue
                if board[nnx][nny] == 'O':
                    cnt += 1
            if cnt >= 2:
                q.append((nx, ny))
                visited[nx][ny] = True
visited = [[False] * m for _ in range(n)]
ans = 0
for i in range(n):
    for j in range(m):
        if board[i][j] == 'O' and not visited[i][j]:
            garo = 0
            saero = 0
            x = i
            y = j
            while x < n and board[x][j] == 'O':
                x += 1
                saero += 1
            while y < m and board[i][y] == 'O':
                y += 1
                garo += 1
            q = deque()
            q.append((i, j))
            visited[i][j] = True
            while q:
                x, y = q.popleft()
                for dx, dy in dxy:
                    nx, ny = x + dx, y + dy
                    if not in_range(nx, ny):
                        continue
                    if visited[nx][ny]:
                        continue
                    if board[nx][ny] != 'O':
                        continue
                    q.append((nx, ny))
                    visited[nx][ny] = True
            if garo > k and saero > k:
                ans += garo * saero
print(ans)

 

 

가로세로 체크하며 이미 방문한 그룹을 다시 방문하지 않기 위해 BFS를 한 번 더 했는데 굳이 이렇게 하지 않고 더 효율적으로 하는 방법이 있습니다. 저는 비효율적으로 구현한것 같습니다.

 

https://boj.ma/31778/t 

 

31778번: PPC 만들기

 

boj.ma

 

 

P와 C로만 이루어진 배열에서 위치변경 행위를 최대 K번 수행해서 부분문자열중 PPC 의 갯수를 최대화 하는 문제입니다.

PPC 부분 문자열의 정의가 "1≤𝑖<𝑗<𝑘≤𝑁 이고, 𝑆𝑖=𝑆𝑗= P, 𝑆𝑘= C인 (𝑖,𝑗,𝑘)의 개수"이기 때문에

P는 최대한 앞으로 C는 최대한 뒤로 옮겨줘 PPPPPP.....CCCCCC 형태가 되어야 PPC 부분문자열의 갯수를 최대화 할 수 있습니다.

그리디하게 위와 같은 형태로 만들었다고 쳐도, PPC 부분 문자열을 세어주는 과정이 남았습니다.

저는 PPC중 가운데 있는 P에 집중했습니다.

 

P를 만날때 마다 (내 앞의 P 갯수) * (내 뒤에 C갯수) 를 정답에 누적해주면 될것 같습니다.

하지만 이걸 모든 P를 만날때마다 하나하나 구해준다면 O(N^2)으로 시간초과입니다.

 

내 앞에 몇개 있는지, 내 뒤에 몇개 있는지 구하는것은 누적합을 통해 최적화가 가능합니다.

 

prefix_p, suffix_c 배열을  통해 O(N)에 전처리하고 P를 만날 때 마다

prefix_p[i - 1] * suffix_c[i] 를 정답에 누적해주면 전체 시간복잡도 O(N)으로 해결 가능합니다.

 

코드(c++)

#include <bits/stdc++.h>

#define endl "\n"
#define NM 100101
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 1000000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
using namespace std;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif

    int n, k;
    cin >> n >> k ;
    vector<char> s(n + 2);
    s[0] = s[n + 1] = 'X';
    for (int i = 1 ; i <= n; i++) cin >> s[i];
    int l = 0, r = n + 1;
    while (l < r && k){ // P는 최대한 앞으로 두고 C는 최대한 뒤로 두기
        while (l < n && s[l] != 'C') l++;
        while (r >= 0 && s[r] != 'P') r--;
        if (l >= r) break;
        k--;
        swap(s[l], s[r]);
    }
    // 누적합
    vector<ll> prefix_p(n + 1), suffix_c(n + 2);
    for (int i = 1; i <= n; i++){
        prefix_p[i] = prefix_p[i - 1] + (s[i] == 'P');
    }
    for (int i = n; i >= 1; i--){
        suffix_c[i] = suffix_c[i + 1] + (s[i] == 'C');
    }

    ll ans = 0;
    for (int i = 1; i <= n; i++){
        if (s[i] == 'P'){ // P를 만날 때마다
            ans += prefix_p[i - 1] * suffix_c[i]; // 내 앞에 P 갯수 * 내 뒤에 C갯수
        }
    }
    cout << ans << endl;
    return 0;
}

 

 

 

 

인덱스 실수로 고생좀 했습니다.

그리고 prefix_p 배열을 굳이 만들지 말고 문자열을 순회하며 P의 갯수를 따로 누적해주는 것만으로도 해결할 수 있습니다.

https://boj.ma/10719/t 

 

10719번: Baekjoon Database Management System

 

boj.ma

 

 

문제 설명은 생략합니다

 

i번째로 읽어야 할 데이터에 대해 선택지가 여러개 있습니다

Area1에 저장된 데이터가 이번에 읽어야 할 데이터와 같다면, 아무 비용도 내지 않고 읽고 넘어가기.

다르다면, i번째 데이터를 c원 내고 저장소에 저장하고 읽고 넘어가기 혹은 저장소는 그대로 두고 i번째 데이터를 읽는 비용을 내고 넘어가기 이렇게 분기가 나누어 질것입니다.

 

완전탐색으로 모든 경우를 해보면 답을 찾을 수 있을것 같습니다.

하지만 n <= 10^5 으로 모든 i번째마다 경우에 따라 분기한다면 지수함수처럼 경우의수가 증가할 것 같습니다.

당연히 시간초과입니다.

 

모든 경우를 보는것이 오래걸리니, 안 봐도 되는 경우에 대해 생각해봤습니다.

예제를 그대로 들고와보면

10 3 5
2 2 4
1 1 1 1 1 2 2 2 3 2

 

9번째 데이터에 3이 처음 나오는데 9번째 데이터를 읽어야 한다고 생각해봅니다, 저장소에는 2가 저장되어있다고 생각해봅니다.

 

현재 9번째 데이터를 읽어야 하고, 저장된 데이터가 2일때 n번째 데이터까지 읽는 최소비용 을 알고싶은건데

9번째 이전까지 어떻게 선택했든, 저장소에 2가 있다면 3을 새로 저장하지 않고 3을 읽는 비용 4원을 내고 마지막 2를 읽고

끝내는게 최소인 것은 항상 똑같을 것입니다.

앞에서 어떻게 다르게 선택하고 9번째까지 오고 저장소에 데이터가 2가 있으면, 최소비용은 항상 일정합니다.

그렇다면 한 번 본 경우는 굳이 다시 안봐도 되겠지요. 따라서 dp로 최적화가 가능합니다.

 

i번째 데이터를 읽어야 하고 j번 데이터가 저장돼있을때 로 재귀함수를 정의합니다

recur(cur, save)

cur에는 1 ~ n번째 데이터까지 들어갈 수 있고

save에는 최대 1~m개의 데이터를 저장할 수 있습니다.

dp배열에 dp[cur][save]상태를 저장할 것이므로 N*M사이즈의 dp배열이 필요합니다.

이 dp배열을 채울것이므로 총 시간복잡도는 O(NM)이 되어 시간내에 통과 가능합니다.

 

"i번째로 읽어야 할 데이터에 대해 선택지가 여러개 있습니다

Area1에 저장된 데이터가 이번에 읽어야 할 데이터와 같다면, 아무 비용도 내지 않고 읽고 넘어가기.

다르다면, i번째 데이터를 c원 내고 저장소에 저장하고 읽고 넘어가기 혹은 저장소는 그대로 두고 i번째 데이터를 읽는 비용을 내고 넘어가기 이렇게 분기가 나누어 질것입니다."

 

이 선택지로 만들어지는 상태들에 대해 그래프로 표현한다면 DAG가 만들어지기 때문에 dp를 적용할 수 있다는것을 알 수 있습니다.

 

 

코드 (c++)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 100000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
using namespace std;
// pbds
//using namespace __gnu_pbds;
//// multi-set
//using ordered_set_equal = tree<int, null_type, less_equal<int>, rb_tree_tag, tree_order_statistics_node_update>;
//// set
//using ordered_set = tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update>;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    int t;
    cin >> t;
    while(t--){
        int n, m, c;
        cin >> n >> m >> c;
        vector<int> data(m + 1), arr(n);
        for (int i = 1; i <= m; i++) cin >> data[i]; // i번 데이터를 읽는 비용
        
        for (int&a : arr) cin >> a; // 읽어야 할 데이터 순서
        
        vector<vector<int>> dp(n, vector<int>(31, -1));
        
        auto recur = [&](const auto&self, int cur, int save){
            if (cur == n) return 0;
            int&ret = dp[cur][save];
            if(~ret) return ret;
            ret = INF;
            if (save == arr[cur]){ // 저장소에 저장된거랑 이번에 읽을거랑 똑같음
                ret = min(ret, self(self, cur + 1, save)); // 비용 안내고 읽고 넘어가기
            }else{ // 저장소랑 현재 데이터랑 다름
             	// 저장소 유지하고 비용 내고 넘어가기
                ret = min(ret, self(self, cur + 1, save) + data[arr[cur]]);
                // 저장소에 현재 데이터 저장하고 저장비용 내고 넘어가기
                ret = min(ret, self(self, cur + 1, arr[cur]) + c); 
            }
            return ret;
        };
        
        cout << recur(recur, 0, 0) << endl;
    }
    return 0;
}

 

 

 

+ Recent posts