1시간 2분 32초
마을의 지도를 1번 집이 root가 되도록 트리로 그렸다.
이떄 각 집마다 가장 적은 비용을 들게 하는 색 c1과 두 번째로 적은 비용을 들게 하는 색 c2를 저장하도록 한다.
만약 부모 집의 색과 현재 집의 c1이 다르다면 그냥 c1을 칠하면 되고,
부모 집의 색과 현재 집의 c1이 같다면 현재 집에는 c2를 칠하면 된다.
#include <iostream>
#include <algorithm>
#include <cmath>
#include <utility>
#include <string>
#include <cstring>
#include <vector>
#include <tuple>
#include <stack>
#include <queue>
#include <deque>
#include <list>
#include <map>
#include <unordered_map>
#include <climits>
#define INF 987654321
#define INF2 2147483647
#define f first
#define s second
#define all(v) (v).begin(), (v).end()
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using tiii = tuple<int, int, int>;
const int MXN = 1e4 + 1;
int N, M;
vector<int> g[MXN];
int colorPrice[MXN];
int parent[MXN];
// pair< pii(첫번째 색, 첫번쨰 점수), pii(두번째 색, 두번쨰 점수) >
vector<tuple<bool, pii, pii>> cost(MXN);
void makeTree(int here) {
for(int there : g[here]) {
if(there == parent[here]) continue;
parent[there] = here;
makeTree(there);
}
}
int paint(int usedColor, int here) {
bool &already = get<0>(cost[here]);
pii &first = get<1>(cost[here]);
pii &second = get<2>(cost[here]);
if(already) {
if(first.f != usedColor) return first.s;
return second.s;
}
int ret = INF;
for(int color = 1; color <= M; color++) {
int res = colorPrice[color];
for(int there : g[here]) {
if(there == parent[here]) continue;
res += paint(color, there);
}
if(color != usedColor) ret = min(ret, res);
if(res < first.s) {
second = first;
first.f = color, first.s = res;
}
else if(res < second.s) {
second.f = color, second.s = res;
}
}
already = true;
return ret;
}
int main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> N;
for(int i=0; i<N-1; i++) {
int a,b; cin >> a >> b;
g[a].emplace_back(b);
g[b].emplace_back(a);
}
cin >> M;
for(int i=1; i<=M; i++) {
cin >> colorPrice[i];
}
// make tree by root 1
makeTree(1);
// init cost
for(int i=1; i<=N; i++) {
cost[i] = make_tuple(false, pii(0,INF), pii(0,INF));
}
// paint tree started by root 1
cout << paint(0,1) << '\n';
return 0;
}
구현은 대체로 무난한데 paint 함수는 구현에 신경써야 하는 부분이 있었다.
처음에는 단순하게 dp[부모 집 색][현재 집 번호]를 생각했지만, (400mb으로) 메모리 부족을 예상할 수 있다.
이때 시간은 얼추 괜찮지만(10^8 정도) 메모리는 부족하다는 것으로부터 생각을 확장시켜 나갔다.
트리 구조에서 부모->자식 방향으로 내려간다고 생각했을 때 부모 집 색만 신경쓰면 된다.
그럼 (현재 노드를 subtree의 root라 할 때) subtree의 비용이 가장 적도록 하는 색 2개만 알고 있으면 해결되겠다는 생각을 했고 그 나머지는 구현만 하면 되는 문제였다.
2시간 50분 38초
거듭제곱 알고리즘을 바로 떠올렸으나 이지만 값이 큰 지 부분 점수만 받을 수 있었다. 이를 최적화시키기 위해 다음과 같은 배열에 값을 저장해서 이용했다.
ll powBy10[MXN]; // powBy10[x] = 10^(2^x)
ll ones[MXN]; // ones[x] = (11...1) % K, (11...1)'s count = 2^x
B의 최댓값이 이기 때문에 이라서 MXN = 60으로 설정했다.
#include <iostream>
#include <algorithm>
#include <cmath>
#include <utility>
#include <string>
#include <cstring>
#include <vector>
#include <tuple>
#include <stack>
#include <queue>
#include <deque>
#include <list>
#include <map>
#include <unordered_map>
#include <climits>
#define INF 987654321
#define INF2 2147483647
#define f first
#define s second
#define all(v) (v).begin(), (v).end()
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using tiii = tuple<int, int, int>;
using pll = pair<ll, ll>;
const int MXN = 60;
ll N, K;
vector<pll> v;
ll powBy10[MXN]; // powBy10[x] = 10^(2^x)
ll ones[MXN]; // ones[x] = (11...1) % K, (11...1)'s count = 2^x
void init_powBy10() {
powBy10[0] = 10%K;
for(int cnt=1; cnt<MXN; cnt++) {
powBy10[cnt] = (powBy10[cnt-1] * powBy10[cnt-1]) % K;
}
}
void init_ones() {
ones[0] = 1;
for(int cnt=1; cnt<MXN; cnt++) {
ones[cnt] = (ones[cnt-1]*powBy10[cnt-1] + ones[cnt-1]) % K;
}
}
int main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> N >> K;
v.resize(N);
for(int i=0; i<N; i++) {
cin >> v[i].f >> v[i].s;
}
init_powBy10();
init_ones();
ll ans = 0;
for(int i=0; i<N; i++) {
ll a = v[i].f, b =v[i].s; int cnt = 0;
while(b) {
if(b%2) ans = (ans*powBy10[cnt] + a*ones[cnt]) % K;
cnt++; b/=2;
}
}
cout << ans << '\n';
return 0;
}
빠른 거듭제곱 알고리즘에 대해서 진득하게 체험해 볼 수 있었던 문제였다.
powBy10과 ones 배열을 생각해내고 이를 내가 원하는 대로 구현하는데 시간이 오래 걸렸다.
그래도 다음부터는 거듭제곱 문제를 비교적 수월하게 풀 수 있을 듯 하다.