W5 · GUIDE.md
GUIDE — 2601 TV화질최적화 통합 해설집
W5 resource-alloc 레인 · 자원 분배 / 예산 제약 파라미터 튜닝 / Lagrangian KKT 채점기:
/home/hm/prj/expert-practice/problems/2601_TV화질최적화/main.cpp(217줄) 베이스라인 점수(user.cpp 원본): 176398 개선풀이(improved/user.cpp): 176540 (+142) 빌드/채점:cd /home/hm/prj/expert-practice && ./run.sh 2601
목차
- 문제 한 줄 요약
- 채점기 역공학 핵심 (ANATOMY 요약)
- 수학적 모델과 최적해 구조
- 공략 알고리즘 단계별 구현
- 개선 풀이(improved/user.cpp) 해설
- 디버그 키트 사용법
- 시험장 30분 실전 타임라인
- 자주 묻는 질문 / 함정
- 웹 참고문헌 및 유사 문제
1. 문제 한 줄 요약
1000개의 시간 스텝 동안, 20개의 TV 화질 feature에 0~100 정수 값을 매번 할당하라. 전력 예산 380을 넘기지 않으면서 사용자 만족도의 총합을 최대화하라.
- 결정 변수:
featureValue[0..19] ∈ {0,…,100}, 매 스텝 갱신 - 목적:
Σ_t (satisfaction_t − 0.01 × power_t)최대화 - 제약: 매 스텝
power_t ≤ 380, 값 범위[0,100],screen_control정확히 1000회 호출 - 위반 시 즉시 −1 (TC 실패)
2. 채점기 역공학 핵심
상세는
ANATOMY.md참조. 여기선 실전에 필요한 핵심만.
2.1 SCORE 방향: 최대화
// main.cpp:121
gTotalScore += sat - powerUsed * 0.01;
- sat(만족도)는 더하고, power는 0.01곱해서 뺀다.
gTotalScore가 클수록 좋다.
2.2 즉사 트리거 3가지
| 트리거 | 코드 | 결과 |
|---|---|---|
power > 380 |
line 113 | gTotalScore=1 → 1000회 미달 → verify -1 |
val ∉ [0,100] |
line 100 | 즉시 gTotalScore=-1 |
screen_control 호출 ≠ 1000 |
line 179 | verify() → -1 |
단 1회 위반으로 TC 전체가 −1. 합법성 0건이 1차 목표.
2.3 PRNG: 완전 결정론적
// main.cpp:189-191
for(int tc = 1; tc <= TC_CASE; tc++){ // TC_CASE=1
gSeed = tc; // gSeed = 1
init();
- LCG:
gSeed = gSeed*25214903917 + 11, 30비트 추출. - 시드 1에서 시작 → 전체 1000스텝의 spotDistance/userDistance 궤적을 100% 복제 가능.
- init에서 860회 소비(feature 20×3 + user 800), 이후 매 스텝
(t%10==0?1:0)+800회 소비.
⭐ 원본 user.cpp의 버그:
advanceSeed(shadowSeed, 861)이 잘못됨. 정확한 오프셋은 860. 861을 쓰면 예측 궤적이 800,800개 셀 중 797,990개(99.6%) 오염.
2.4 시간 제한: 사실상 무제한
- main.cpp에
clock()/TIME_LIMIT없음. - 유일한 시간은 스텝 카운터
gCurrentTime(0→999). - 실기기 글로벌 타임아웃(보통 3초)만 피하면 됨. 스텝당 수만 회 연산 가능.
2.5 API (내가 호출할 수 있는 것)
void feature_info(int power[], double quality[], int median[]); // 정적 정보
void user_info(int userDistance[]); // 현재 거리
long long satisfaction(int featureValue[]); // 만족도(※ 함정: clamp 없음)
void screen_control(int featureValue[]); // ★ 유일한 액션
함정: 외부
satisfaction()은 거리 clamp가 없고, 실제 SCORE용user_satisfaction()은 clamp가 있다. 자체 평가 함수에는 clamp를 넣어야 정확.
3. 수학적 모델과 최적해 구조
3.1 스텝별 최적화 문제
매 스텝 t에서 풀어야 할 문제:
최대화 sat_t(val) = Σ_i α_i(dist_t) · val_i
제약 power_t(val, prev) = Σ_i [ power_i · val_i²/10000 + 0.01·|val_i − prev_i| ] ≤ 380
0 ≤ val_i ≤ 100, val_i ∈ ℤ
여기서 α_i(dist) = 0.01 · quality_i · Σ_u 1/(|median_i − dist_u| + 1) (거리가 주어지면 상수).
3.2 왜 이 문제는 'water-filling'인가
- sat은 val에 선형, power는 val에 이차.
- 이는 정보이론의 고전 문제 water-filling(Boyd & Vandenberghe Convex Optimization Example 5.2)과 동일 구조.
- 연속 완화 해(KKT 조건):
val_i = 5000 · α_i / (λ · power_i)
여기서 λ는 Lagrange 승수. Σ power_i val_i²/10000 = 380이 되도록 λ를 이분 탐색.
- α_i가 크고 power_i가 작을수록 val_i가 크다 → '물이 낮은 곳(효율 좋은 채널)으로 찬다'.
3.3 정수화 손실과 보정
KKT 연속해를 반올림하면 정수 val이 되지만: 1. 예산이 380을 초과할 수 있음 → α/Δpower 비율 최악 feature부터 −1 (greedy reduce) 2. 예산이 남을 수 있음 → α/Δpower 비율 최고 feature에 +1 (greedy fill) 3. 같은 power로 더 큰 sat을 얻는 자리 교환 → 2-swap
3.4 시간 결합(switching cost)의 영향
0.01·|val_i − prev_i|가 인접 스텝을 결합.- 실측: switch cost 총합 ≈ 162 / 1000스텝 = 0.16/스텝.
- sat 대비 1000배 작음 → 사실상 독립 per-step 최적화로 전역 최적에 근접.
- 따라서 복잡한 lookahead/MPC는 불필요 (오히려 해침, ATTACK §4.2).
4. 공략 알고리즘 단계별 구현
4.1 1단계: 합법성 확보 (val=0 또는 균등 k)
void process(){
int fp[20]; double fq[20]; int fm[20];
feature_info(fp, fq, fm);
int sumP = 0; for(int i=0;i<20;i++) sumP += fp[i];
int k = (int)sqrt(380.0*10000.0/sumP);
if(k>100) k=100; if(k<0) k=0;
int val[20]; for(int i=0;i<20;i++) val[i]=k;
for(int t=0; t<1000; t++) screen_control(val);
}
→ 균등 분배. 수만 점 획득 + 합법. 시험장 3분 컷.
4.2 2단계: per-step KKT λ 이분 탐색
// 매 스텝:
double alpha[20]; for(int i=0;i<20;i++) alpha[i] = computeAlpha(i, userDist);
int val[20]; for(int i=0;i<20;i++) val[i]=100;
if(totalPower(val,prev) <= 380.0) goto submit; // 예산 남으면 전부 100
// λ 이분 탐색
double lo=1e-15, hi=1e3;
for(int it=0; it<300; it++){
double mid = (lo+hi)*0.5;
for(int i=0;i<20;i++){
double v = 5000.0*alpha[i]/(mid*fp[i]);
val[i] = (int)(min(max(v,0.0),100.0) + 0.5);
}
if(totalPower(val,prev) > 380.0) lo=mid; else hi=mid;
}
// hi(예산 이하 쪽)로 최종 val
for(int i=0;i<20;i++){
double v = 5000.0*alpha[i]/(hi*fp[i]);
val[i] = (int)(min(max(v,0.0),100.0) + 0.5);
}
submit:
screen_control(val);
for(int i=0;i<20;i++) prev[i]=val[i];
→ ~170,000점. 가장 큰 도약.
4.3 3단계: greedy reduce + greedy fill (예산 정밀 사용)
// 예산 초과 시: α/Δpower 최악부터 -1
while(totalPower(val,prev) > 380.0+1e-9){
int bi=-1; double br=1e18;
for(int i=0;i<20;i++){
if(val[i]<=0) continue;
double dP = powerDelta(val,prev,i,-1); // -1 했을 때 power 변화
double dS = -alpha[i]; // sat 변화
double r = dS/dP; // 비효율(작을수록 먼저 깎음)
if(r<br){ br=r; bi=i; }
}
if(bi<0) break;
val[bi]--;
}
// 예산 남으면: α/Δpower 최고부터 +1
bool prog=true;
while(prog){
prog=false;
int bi=-1; double br=-1;
for(int i=0;i<20;i++){
if(val[i]>=100) continue;
double dP = powerDelta(val,prev,i,+1);
if(dP<=1e-15) continue;
double r = alpha[i]/dP;
if(r>br){ br=r; bi=i; }
}
if(bi>=0){
val[bi]++;
if(totalPower(val,prev) > 380.0+1e-9){ val[bi]--; break; }
prog=true;
}
}
→ ~176,000점.
4.4 4단계: 2-swap local search
// (a-1, b+1) 또는 (a+1, b-1)로 sat 증가 가능하면 교환
bool improved=true;
while(improved){
improved=false;
for(int a=0;a<20 && !improved;a++){
for(int b=0;b<20;b++){
if(a==b||val[a]<=0||val[b]>=100) continue;
double dP = powerDelta(val,prev,a,-1)+powerDelta(val,prev,b,+1);
double dS = -alpha[a]+alpha[b];
if(dS>1e-12 && totalPower(val,prev)+dP<=380.0+1e-9){
val[a]--; val[b]++; improved=true; break;
}
}
}
}
→ 176,540점 (최종).
5. 개선 풀이(improved/user.cpp) 해설
파일: improved/user.cpp
5.1 베이스라인 대비 변경점
| 항목 | 원본 user.cpp | improved | 효과 |
|---|---|---|---|
| PRNG offset | advanceSeed(861) |
860 (또는 미사용) |
예측 궤적 정확화 |
| lookahead 블렌딩 | 70% cur + 30% future | 제거 (per-step만) | +142점 |
| swap 로직 | 조건부 | 양방향(a-1,b+1 / a+1,b-1) | +수 점 |
| 안전 마진 | 없음 | BUDGET+1e-9 |
float 위험 제거 |
5.2 핵심 통찰
lookahead가 오히려 점수를 깎는다. switch cost가 sat 대비 1000배 작아, 미래 α를 섞으면 현재 α의 정밀도만 떨어뜨린다. 원본의 PRNG 예측 1000줄은 사실상 무의미. per-step KKT + greedy + 2-swap이 이 문제의 정답이다.
5.3 빌드 및 검증
cd /home/hm/expert_research/W5_resource_alloc
DIR=/tmp/verify_2601; mkdir -p $DIR
cp /home/hm/prj/expert-practice/problems/2601_TV화질최적화/main.cpp $DIR/main.cpp
cp improved/user.cpp $DIR/user.cpp
g++ -O2 -w -std=c++17 -o $DIR/run $DIR/main.cpp $DIR/user.cpp && $DIR/run
# 예상 출력: TC 1: Score = 176540
6. 디버그 키트 사용법
파일: debug_snippet.cpp · 해설: DEBUG_KIT.md
6.1 user.cpp에 통합 (3줄)
#include "debug_snippet.cpp" // 상단
// ...
void process(){
dbg_init();
feature_info(fp,fq,fm);
user_info(ud0);
dbg_input_stats(fp,fq,fm,ud0); // 1회: 입력 통계
// ...
for(int t=0;t<1000;t++){
// val 계산 ...
dbg_step(t, val, prev, alpha, powerOf(val,prev)); // 매 스텝
screen_control(val);
}
dbg_summary(/*finalScore*/); // 루프 후: SCORE 분해
}
6.2 출력 예시 (실측)
=== INPUT STATS ===
Features: 20, Users: 800
power: sum=894 min=12 max=80
quality: sum=29.63 min=0.10 max=2.37
[HINT] quality 편차 큼 → 소수 feature 집중 효과적
[t= 0] power=379.99/380 (100.00%) sat=104.437 switch=12.540
[t= 700] power=380.00/380 (100.00%) sat=322.540 switch=0.070
=== SCORE BREAKDOWN ===
Sum Satisfaction: 180340.24 (avg 180.34/step)
Sum Power : 379989.47 (avg 379.99/step)
└ switch cost : 180.46
Power Slack : 10.53 total (0.01/step)
=== CONSTRAINT CHECK ===
screen_control calls: 1000/1000 OK
power>380 violations: 0 OK
out-of-range val : 0 OK
=== DIAGNOSTIC HINTS ===
[HINT] local-search + < 10 → 이미 최적 근처
6.3 의사결정표
| 관찰 | 다음 액션 |
|---|---|
| Power Slack > 50 | greedy fill 패스 ↑, λ 정밀도 ↑ |
| Power Slack < 1 | 안전 마진 0.5 추가 |
| OVERBUDGET > 0 | λ upper ↑, trim 보수화 |
| switch > 1000 | val 평활화 / lookahead(윈도 3) |
| local-search + < 10 | SA 또는 다른 초기 λ |
7. 시험장 30분 실전 타임라인
[0:00~0:03] main.cpp 읽기 → SCORE 방향, power 380 제약, PRNG 결정론적 파악
[0:03~0:06] 합법성 확보 (val=균등 k) → 수만 점 기본 보장
[0:06~0:15] per-step KKT λ 이분탐색 구현 → ~170,000점
[0:15~0:22] greedy reduce + greedy fill 추가 → ~176,000점
[0:22~0:28] 2-swap local search → ~176,540점
[0:28~0:30] 안전 마진 점검 + dbg_summary() 1회 출력 → 제출
시간이 남으면 (30분+): - SA 폴란드 (스텝당 2000회 random ±1 move, 전부 sat↑이면 accept) - 3-swap, 4-swap 확장 - 여러 초기 λ에서 시작해 최적 선택 - 단, lookahead/PRNG 예측은 이 문제에서 역효과 → 시도하지 말 것
8. 자주 묻는 질문 / 함정
Q1. 왜 power가 380.0001만 넘어도 -1인가?
A. main.cpp:113의 powerUsed > POWER_BUDGET가 단정문이기 때문. float 오차라도 즉사. 반드시 BUDGET+1e-9 마진.
Q2. screen_control을 999번만 호출하면?
A. verify()(line 179)가 gCurrentTime != 1000 체크 → -1. 루프는 t < 1000로 정확히.
Q3. lookahead가 왜 점수를 깎나요? A. switch cost(0.16/스텝)가 sat(180/스텝)의 1/1000. 미래 α를 블렌딩하면 현재 α의 정밀도만 떨어져 per-step 최적에서 벗어남. 실측: lookahead 제거 시 +142점.
Q4. PRNG를 예측해야 하나요? A. 이 문제에선 아니오. switch cost가 작아 예측이 도움 안 됨. 단, 정확한 오프셋은 860이고 원본 user.cpp의 861은 버그.
Q5. val_i가 100을 넘으면?
A. line 100에서 즉시 -1. KKT 직후 반드시 if(val[i]>100) val[i]=100.
Q6. SA를 써야 하나요? A. 이 문제에선 좌표하강(KKT+greedy+2-swap)이 SA보다 우월. SA는 sat이 선형이라 국소해가 거의 전역해. 실험 결과 SA 추가 효과 < 5점.
Q7. 여러 TC에 대비하려면?
A. main.cpp의 TC_CASE가 1로 고정되어 있으므로 실제는 단일 TC. 다만 시드가 바뀌면 α 분포가 달라지므로, KKT λ 범위를 넓게([1e-15, 1e3]) 잡고 greedy fill을 충분히 돌리면 강건.
9. 웹 참고문헌 및 유사 문제
9.1 이론 (Lagrangian / water-filling)
- Convex Optimization (Boyd) Example 5.2 Water-filling — water_filling.pdf: 본 문제의 수학적 구조와 동일. KKT 닫힌형 해
val_i = 5000·α_i/(λ·power_i)의 근거. - Lagrangian relaxation (Wikipedia) — en.wikipedia.org/wiki/Lagrangian_relaxation: 예산 제약을 느슨하게 풀고 λ로 다루는 기법의 개요.
- KKT Conditions (CMU Convex Opt) — stat.cmu.edu/~ryantibs/convexopt-S15/lectures/12-kkt.pdf: KKT 필요충분조건과 water-filling 그림.
- Lagrangian Relaxation for Resource Allocation (Wiley) — onlinelibrary.wiley.com/doi/10.1111/mice.13223: capacity/distance 제약 자원 분배에 LR 적용.
9.2 알고리즘 (greedy marginal allocation)
- Greedy Power Allocation (SCIRP) — scirp.org/journal/paperinformation?paperid=19563: OFDM에서 power budget 하에서 greedy bit/power allocation. 본 문제의 greedy fill과 동일 원리.
- Optimal Utility Design of Greedy Algorithms (arXiv) — arxiv.org/html/2204.10364v2: best-response greedy의 최적성 조건.
9.3 휴리스틱 최적화 대회 (AHC / TopCoder Marathon)
- TopCoder Marathon Match Training — discussions.topcoder.com/discussion/7583: MM 최적화 문제 훈련 자료 모음.
- TopCoder Marathon: Approaching the Task (SA) — topcoder.com/blog/approaching-marathon-match-task-pt-1: Simulated Annealing 실전 가이드.
- eulerscheZahl의 MM 풀이 모음 — github.com/eulerscheZahl/CodingContests: 다양한 MM 풀이 코드.
- AtCoder Heuristic Contest — atcoder.jp/contests/hec: AHC 시리즈. 특히 ahcXXX의 자원 분배/파라미터 튜닝 문제가 본 문제와 유사.
9.4 본 문제와 가장 유사한 클래식 문제
- AtCoder DP Contest E - Knapsack 2 — atcoder.jp/contests/dp/tasks/dp_e: 배당/가치/무게의 정수 할당. 본 문제는 '매 스텝 독립 knapsack'의 연속 버전.
- Single Knapsack with Budget Constraint — researchgate.net/publication/280597878: 예산 제약 knapsack 휴리스틱.
산출물 인덱스
| 파일 | 내용 |
|---|---|
ANATOMY.md |
채점기 7단계 해체 (최우선) |
ATTACK.md |
공략 우선순위와 시간 배분 |
DEBUG_KIT.md |
개선 힌트 출력 설계 |
debug_snippet.cpp |
복붙 가능 디버그 함수 |
improved/user.cpp |
개선 풀이 (176540, +142) |
GUIDE.md |
본 문서 (통합 해설집) |