📝 문제 정보 # 링크: https://www.acmicpc.net/problem/30478 문제 # 러시아워입니다! 오늘 퇴근 후, 쇼핑몰이 닫기 전에 가족 모두에게 줄 사탕을 사야 합니다.
가족들은 독점성과 균일성을 매우 중요하게 여기기 때문에, 당신은 그들을 감동시키기 위한 계획을 세웠습니다. 각 가족 구성원에게 주는 사탕은 모두 단일 브랜드여야 하며, 동일한 브랜드의 사탕을 다른 가족 구성원이 받아서는 안 됩니다. 또한, 누군가를 더 사랑한다는 사실을 들키고 싶지 않기 때문에 모든 가족 구성원이 같은 수의 사탕을 받아야 합니다.
📝 문제 정보 # 링크: https://www.acmicpc.net/problem/11493 🧐 관찰 및 접근 # 동전들을 swap해서 잘 옮겨서 맞춰야한다.. 일단 색을 모두 신경쓰긴 싫으니까, 1의 위치를 맞춘다고만 생각하자. 뭔가 이분그래프적으로 생각할 수 있지 않을까? 이게 움직이는것만 신경쓰면 플로이드워셜 + 이분그래프 매칭 최소비용…으로 하면 되는거같은데? 근데 swap이다보니 이렇게 맘대로 하는것보다 효율적인 방법이 무시될 것 같다. 일단 이분그래프로 생각을 좀 해보긴 하자. 그림을 그려보자. 그림 1의 예시이다. 어우 난잡해 ㅋㅋ 뭔가 증가경로 맛처럼 할 수 있는거같기는 한데.. ….가 아니라 그냥 이상태로 최소비용 최대유량 아닌가? 끝이네? 이분그래프로 분할할 필요도 없이, 그냥 유량으로 달리면 된다. 💻 풀이 # 코드 (C++): void solve(){ int N, M; cin >> N >> M; MinCostMaxFlow MCMF(N+2); MCMF.setST(0, N+1); rep(i, 0, M){ int u, v; cin >> u >> v; MCMF.add(u, v, 1e9, 1); MCMF.add(v, u, 1e9, 1); } rep(i, 1, N+1){ int x; cin >> x; if(x == 1) MCMF.add(0, i, 1, 0); } rep(i, 1, N+1){ int x; cin >> x; if(x == 1) MCMF.add(i, N+1, 1, 0); } cout << MCMF.match() << '\n'; } 🔒 구현 코드 잠금
📝 문제 정보 # 링크: https://www.acmicpc.net/problem/34609 번역 문제 # $1$번부터 $n$번까지 번호가 매겨진 $n$송이의 꽃이 왼쪽에서 오른쪽으로 일렬로 놓여 있다. 각 꽃은 백합(lily) 또는 장미(rose) 중 하나이다. $0$ 이상 $n$ 이하의 정수 $j$에 대해, $l_j$를 왼쪽 $j$개의 꽃 중 백합의 수, $r_j$를 오른쪽 $n - j$개의 꽃 중 장미의 수라 하자.
📝 문제 정보 # 링크: https://www.acmicpc.net/problem/28359 🧐 관찰 및 접근 # 일단 모두 $P$에 몰아넣거나, $Q$에 몰아넣거나는 가능하니 일단 기본적으로 모든 원소의 합정도는 여유롭게 가능하다. 그런데, 모든 수가 같다고 생각해보자. 그러면 모든 수는 $P$와 $Q$에 동시에 속하는데… 동시에 속할 수 있는 수를 어떻게 제어할 수 있지? 동시에 속한 수가 서로 다른 여러개의 수일 수는 없다. 어떤 수와 그 개수를 곱한 값이 최대인 수를 골라서, 맨 뒤로 보내자. 💻 풀이 # 코드 (C++): void solve(){ int N; cin >> N; map<int, int> mp; int ans = 0; rep(i, 0, N){ int x; cin >> x; mp[x]++; ans += x; } int mxIdx = -1, mxVal = -1; for(auto [k, v]: mp){ if(k*v > mxVal){ mxVal = k*v; mxIdx = k; } } ans += mxVal; vector<int> v1, v2; for(auto [k, v]: mp){ if(k < mxIdx) rep(i, 0, v) v1.push_back(k); else if(k > mxIdx) rep(i, 0, v) v2.push_back(k); } rrep(i, 0, v2.size()) v1.push_back(v2[i]); rep(i, 0, mp[mxIdx]) v1.push_back(mxIdx); cout << ans << '\n'; for(auto x: v1) cout << x << ' '; } 🔒 구현 코드 잠금
📝 문제 정보 # 링크: https://www.acmicpc.net/problem/1006 🧐 관찰 및 접근 # 유명한 통곡의 벽 문제 초라기 문제가 $2 \times N$의 원형이라 조금 곤란하다. 쉬운 경우부터 생각하자. $1 \times N$의 선형이라면, 계단 수처럼 DP로 풀 수 있을 것 같다. $2 \times N$의 선형이라도 DP로 가능한 것 같다. 두칸 전에서 오는 것에 대해 가로두개 / 위에 가로와 아래 따로 / 아래 가로와 위에 따로 한칸 전에서 오는 것에 대해 세로 / 위아래 따로 이 5가지 경우에서 가능한 것 같다! 그런데 원형이라 조금 곤란한데.. 뭔가 케이스를 나눌 수 있을 것 같긴 하다. 선형으로 생각했을때 맨 왼쪽에 대해, 그게 반대쪽과 이어진경우 / 안이어진경우. 위아래니까 각각 두가지, 총 4가지 경우의수를 따지면 될듯? 근데 이걸 어떻게 깔끔하게 코딩하지? 큰일났다. 둘다 반대쪽과 이어진 경우는 문제를 한칸 밀어서 풀기만 하면 되는 것 같은데, 한쪽만 밀린 경우가 까다롭다. 그런데 사실 모든 칸이 지그재그로 연결된 경우 빼고는 언젠가는 선형으로 풀어도 되는 타이밍이 있는 것 같다! 위에서 말한 타이밍은 예제에서는 $1-2, 10-11, 3-4, \cdots$로 연결된 느낌과 같다. 시간복잡도가 $O(NW)$이 되긴 하는데, 돌만하지 않을까? 저 지그재그만 예외처리를 해버리자. ㄲㅂ 시간초과네. 테케가 여러개라 그런 것 같다. 엥? 이거 그냥 길이를 $2N$으로 늘려서 하면 되지 않을까? …인줄알았는데 전이식 자체부터 두개 전에서 땡겨오다보니 차분트릭이 안먹히는것 같다. 전이식 자체에서도 여러칸 단위로 지그재그로 오면 할 수 있는게 없다.. DP에서, 상태공간을 조금 더 정의해보자. $\text{DP}[i][j]$라고 하면, i번째칸까지 봤을때 위아래 찬게 j상황이라고 생각해보자. $j = 0, 1, 2, 3$에서 위아래 모두 빔, 위 참, 아래 참, 위아래 모두 참과 같은 느낌이다. 이렇게해서 전이식을 잘 세우면 저 예외상황들을 처리하기 용이할 것 같다. 3번은 0번과 같으니 없애고, 나머지 3개로 정말 잘 처리해보자… 💻 풀이 # 코드 (C++): void solve(){ ll N, W; cin >> N >> W; vector<array<ll, 2>> v(N); rep(i, 0, N) cin >> v[i][0]; rep(i, 0, N) cin >> v[i][1]; auto calc = [&](bool con1, bool con2) { vector<array<ll, 3>> DP(N+1); // 0: 둘다끝, 1: 위가 튀어나감, 2: 아래가 튀어나감 rep(i, 0, N+1) rep(j, 0, 3) DP[i][j] = 1e18; DP[0][0] = 0; if(con1) DP[0][1] = 0; if(con2) DP[0][2] = 0; if(con1 && con2) DP[1][0] = 0; rep(i, 1, N+1){ DP[i][0] = min(DP[i][0], DP[i-1][0] + (v[i-1][0] + v[i-1][1] <= W ? 1 : 2)); DP[i][0] = min(DP[i][0], DP[i-1][1] + 1); DP[i][0] = min(DP[i][0], DP[i-1][2] + 1); if(i-2 >= 0 && v[i-2][0]+v[i-1][0] <= W && v[i-2][1]+v[i-1][1] <= W){ DP[i][0] = min(DP[i][0], DP[i-2][0] + 2); } DP[i][1] = min(DP[i][1], DP[i][0] + 1); if((i == N && con1) || (i < N && v[i-1][0] + v[i][0] <= W)){ DP[i][1] = min(DP[i][1], DP[i-1][0] + 2); DP[i][1] = min(DP[i][1], DP[i-1][2] + 1); } DP[i][2] = min(DP[i][2], DP[i][0] + 1); if((i == N && con2) || (i < N && v[i-1][1] + v[i][1] <= W)){ DP[i][2] = min(DP[i][2], DP[i-1][0] + 2); DP[i][2] = min(DP[i][2], DP[i-1][1] + 1); } } if(!con1 && !con2) return DP[N][0]; if(con1 && !con2) return DP[N-1][2] + 1; if(!con1 && con2) return DP[N-1][1] + 1; if(con1 && con2) return DP[N-1][0] + 2; }; auto origin = v; ll ans = 2*N; rep(con1, 0, 2) rep(con2, 0, 2){ if(con1 && v[0][0] + v[N-1][0] > W) continue; if(con2 && v[0][1] + v[N-1][1] > W) continue; ans = min(ans, calc(con1, con2)); } cout << ans << '\n'; } 🔒 구현 코드 잠금