Minimum-Score Slalom
National Olympiad — National round (2016 season)
Submission name: slalom.cpp
Problem summary
Pass gates in index order, optionally skipping any gate. Each visited gate and the finish add one point; travelled straight-line distances and skipped-gate penalties also add to the score. Find the smallest total.
Source clarification. One original score sentence prints a minus before penalties, but the stated penalty rule and official sample require adding them. This implementation follows that intended nonnegative penalty convention.
Input
Start and finish coordinates, M, then M rows x y penalty.
Output
The optimum score to three digits after the decimal point.
Constraints
0 ≤ M ≤ 25; coordinates and penalties are integers from 0 to 1000.
Worked example
Input and output for the worked example discussed below.
Input
0 0 100 100 3 30 30 90 60 60 80 10 90 10
Output
154.421
Solution analysis
- Treat the start as vertex 0 and the finish as M+1. If the previous visited vertex is i and the next is j>i, the transition adds their Euclidean distance, one visit point, and all penalties of gates strictly between them.
- Use prefix sums of penalties to obtain skipped penalties in O(1). Then dp[j] is the minimum over every earlier i of dp[i]+distance(i,j)+1+skippedPenalty(i,j), with dp[0]=0. This is a shortest path in an index-ordered DAG.
- In the sample, visit gates 1 and 2 and skip gate 3. Start, gate 1, gate 2 and finish lie on the main diagonal, so distance is sqrt(20000); add three visit points and penalty 10, yielding 154.421.
Why it works
The last visited predecessor partitions every valid route uniquely into an optimal earlier route and one transition containing exactly its skipped penalties.
Watch out. The start earns no visit point, while the finish does. Penalties are added; the original text contains a contradictory minus sign in one sentence, but its penalty rule and sample require addition.
Commented C++17 solution
An independently written complete program with standard headers.
// The last visited predecessor partitions every valid route uniquely into an optimal earlier route and one
// transition containing exactly its skipped penalties..
#include<cmath>
#include<iomanip>
#include<iostream>
#include<limits>
#include<vector>
using namespace std;
int main(){
int sx,sy,fx,fy;
cin>>sx>>sy>>fx>>fy;
int gates;
cin>>gates;
vector<int>x(gates+2),y(gates+2),penalty(gates+2),prefix(gates+2);
x[0]=sx;
y[0]=sy;
x[gates+1]=fx;
y[gates+1]=fy;
for(int gate=1;gate<=gates;gate++){
cin>>x[gate]>>y[gate]>>penalty[gate];
prefix[gate]=prefix[gate-1]+penalty[gate];
}
vector<double>best(gates+2,numeric_limits<double>::infinity());
best[0]=0;
for(int next=1;next<=gates+1;next++){
for(int previous=0;previous<next;previous++){
double distance=hypot(x[next]-x[previous],y[next]-y[previous]);
best[next]=min(best[next],best[previous]+distance+1+prefix[next-1]-prefix[previous]);
}
}
cout<<fixed<<setprecision(3)<<best.back()<<'\n';
}