Problems – Dynamic Programming

Problems – Dynamic Programming
Problems
11.6.1 (Is Bigger Smarter?) Some people think that the bigger an elephant is, the smarter it is. To
disprove this, you want to analyze a collection of elephants and place as large a subset of
elephants as possible into a sequence whose weights are increasing but IQs are decreasing.
Input
The input will consist of data for a bunch of elephants, at one elephant per line terminated
by the end-of-file. The data for each particular elephant will consist of a pair of integers: the
first representing its size in kilograms and the second representing its IQ in hundredths of IQ
points. Both integers are between 1 and 10,000. The data contains information on at most
1,000 elephants. Two elephants may have the same weight, the same IQ, or even the same
weight and IQ.
Output
The first output line should contain an integer n, the length of elephant sequence found.
The remaining n lines should each contain a single positive integer representing an elephant.
Denote the numbers on the i-th data line as W [i] and IQ[i]. If this sequence of n elephants
is (el1 , el2 , . . . , eln ) then it must be the case that
W [el1 ] < W [el2 ] < ... < W [eln ] and IQ[el1 ] > IQ[el2 ] > . . . > IQ[eln ]
In order for the answer to be correct, n must be as large as possible. All inequalities are strict:
weights must be strictly increasing, and IQs must be strictly decreasing. Your program can
report any correct answer for a given input.
Answer. Suppose we read the input in an N-dimensional vector v = hv[0], v[1], . . . , v[N − 1]i
of instances of the structure
struct Elephant {int id,W,IQ; }
where v[j].id = j + 1, and v[j].W, v[j].IQ are the weight and IQ of the j + 1-th elephant read
from the input. To simplify the problem, we assume that v is sorted in-place such that, for
all 0 ≤ i < j < N, we have v[i].W < v[j].W or (v[i].W = v[j].W and v[i].IQ ≥ v[j].IQ). Also, let’s
assume that SNi is the sequence of numbers (i, i + 1, . . . , N − 1) for all 0 ≤ i < N.
To compute a desired sequence el = (el1 , . . . , eln ), we reason as follows:
1
I We compute a longest subsequence e = (e1 , e2 , . . . , en ) of SN0 such that
v[ei ].W < v[ej ].W
and v[ei ].IQ > v[ej ].IQ whenever 1 ≤ i < j ≤ n.
I We define elj := v[ej ].id for all 1 ≤ j ≤ n.
The sequence e can be computed with dynamic programming, as follows.
(a) Let es be the vector hes[0], es[1], . . . , es[N − 1]i where
es[i] = (ei,1 , ei,2 , . . . , ei,l[i] )
is a longest subsequence of SNi which fulfils the conditions
P(i): ei,1 = i, v[ei,j ].W < v[ei,k ].W and v[ei,j ].IQ > v[ei,k ].IQ whenever i ≤ j < k ≤ l[i].
If we manage to compute es, we can choose e to be the longest component of es.
(b) The sequences es[i] can be computed for i from N-1 downto 0:
I es[N − 1] := (N − 1) because (N − 1) is the longest subsequence of SNN−1 = (N − 1)
for which condition P(N − 1) trivially holds.
I If 0 ≤ i < N − 1, a longest increasing subsequence of SNi for which condition P(i)
holds is either (1) es[i] = (i, j, . . . , ej,l[j] ) if there exists es[j] a longest sequence
|
{z
}
es[j]
with i < j < N such that v[i].W < w[j].W and v[i].IQ > v[j].IQ, or else (2) es[i] = (i).
A C++ implementation of this approach is shown below.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Elephant { int id=1, W, IQ; };
bool cmp(Elephant& a, Elephant& b) { return (a.W<b.W||(a.W==b.W && a.S>b.S)); }
int main() {
Elephant E;
vector<Elephant> v;
while (cin>>E.W>>E.IQ) { v.push_back(E);E.id++; }
sort(v.begin(),v.end(),cmp);
int N=v.size(),idx=N-1;
vector<vector<int>> es(N);
es[N-1].push_back(N-1);
for (int i=N-2;i>=0;i--) {
es[i].push_back(i);
for (int j=i+1;j<N;j++)
if (es[i].size()<1+es[j].size()&&(v[i].W<v[j].W) &&(v[i].IQ>v[j].IQ)) {
es[i]=es[j]; es[i].push_back(i);
}
if (es[idx].size()<es[i].size()) idx=i;
}
2
cout<<es[idx].size()<<endl;
for (int i=es[idx].size()-1;i>=0;i--)
cout<<v[es[idx][i]].id<<endl;
return 0;
}
3
11.6.2 (Distinct Subsequences) A subsequence of a given sequence S consists of S with zero or more
elements deleted. Formally, a sequence Z = z1 z2 . . . zk is a subsequence of X = x1 x2 . . . xm
if there exists a strictly increasing sequence hi1 , i2 , . . . , ik i of indices of X such that for all
j = 1, 2, . . . , k, we have xij = zj . For example, Z = bcdb is a subsequence of X = abcbdab
with corresponding index sequence h2, 3, 5, 7i. Your job is to write a program that counts
the number of occurrences of Z in X as a subsequence such that each has a distinct index
sequence.
Answer. Suppose N (i, j) is the number of occurrences of zi . . . zk as a subsequence in
xj . . . xm , where 1 ≤ i ≤ k and 1 ≤ j ≤ m. We have to compute N (1, 1). In order to do this,
we look for a recursive formula to compute N (i, j). We distinguish two cases:
(a) If k − i > m − j then N (i, j) = 0.
N (i, j + 1) if Z[i] = X[j],
(b) N (i, j) =
0
otherwise.
The DAG of dependencies between the values of N (i, j) is
N (1, 1)
N (2, 1)
..
.
N (k − 1, 1)
N (k, 1)
T [1]
N (1, 2)
...
N (1, m) = 0
N (2, 2)
...
N (2, m) = 0
N (k − 1, 2)
...
N (k − 1, m) = 0
N (k, 2)
...
N (k, m)
T [2]
...
T [m]
..
.
..
.
..
.
We can compute iteratively values of the vector T [j] = hN (1, j), . . . , N (k, j)i from j = m
downto 1, in order to get the first component of T [1]:
T=[0,0,...,0,b] where b=1 if xm == zk , and b=0 otherwise
for j=m-1 downto 1
for i=1 to k
if k − i > m − j
T[i]=0
else
if (Z[i]==X[j])
if i==k
T[i]=T[i]+1
else
T[i]=T[i]+T[i+1]
return T[1]
A C++ implementation of this approach is shown below (note that in C++, vector elements
are indexed starting from 0):
4
#include <iostream>
#include <vector>
using namespace std;
string X,Z;
// number of distinct subsequences
int ds() {
int k = Z.size(), m = X.size();
vector<int> T(k);
for (int i=0;i<k;i++) T[i]=0;
if (Z[k-1]==X[m-1]) T[k-1] = 1;
for (int j=m-1;j>=1;j--)
for (int i=1;i<=k;i++) {
if(k-i>m-j) T[i-1]=0;
else {
if (Z[i-1]==X[j-1])
T[i-1]+=(i==k?1:T[i]);
}
}
return T[0];
}
int main() {
int n;
cin >> n;
for (int i=0;i<n; i++) {
cin >> X >> Z;
cout << ds() << endl;
}
return 0;
}
5
11.6.3 (Weights and Measures) A turtle named Mack, to avoid being cracked, has enlisted your
advice as to the order in which turtles should be stacked to form Yertle the Turtle’s throne.
Each of the 5,607 turtles ordered by Yertle has a different weight and strength. Your task is
to build the largest stack of turtles possible.
Input
Standard input consists of several lines, each containing a pair of integers separated by one
or more space characters, specifying the weight and strength of a turtle. The weight of the
turtle is in grams. The strength, also in grams, is the turtle’s overall carrying capacity,
including its own weight. That is, a turtle weighing 300 g with a strength of 1,000 g can
carry 700 g of turtles on its back. There are at most 5,607 turtles.
Output
Your output is a single integer indicating the maximum number of turtles that can be stacked
without exceeding the strength of any one.
Răspuns. Fie hg1 , c1 i, hg2 , c2 i, . . . , hgn , cn i perechile greutate/capacitate ale fiecărei ţestoase,
citite de la intrarea standard. Deoarece ţestoasele i cu ci < gi nu pot face parte din nici o
stivă, putem presupune că omitem să citim astfel de perechi hgi , ci i de la intrarea standard.
Prin urmare, vom considera că am citit 0 ≤ n ≤ 5607 perechi hgi , ci i cu ci ≥ gi .
Fie p ≤ n numărul maxim de ţestoase care pot forma o stivă. Dacă n = 0 atunci p = 0.
În continuare vom presupune că n > 0. În acest caz există i1 , . . . , ip indicii unei stive de p > 0
ţestoase, enumerate de la vârf la baza stivei. Acest lucru ı̂nseamnă că au loc inegalităţile
(Pk ): cik ≥
k
X
gij , adică ţestoasa ik poate ţine ı̂n spate ţestoasele i1 , . . . , ik−1
j=1
pentru 1 ≤ k ≤ p. Este util să se observe că putem presupune că cik < cik+1 pentru toţi
1 ≤ k < p deoarece, dacă cik > cik+1 putem permuta locurile ţestoaselor ik şi ik+1 ı̂n stivă
fiindcă:
• Inegalităţile (Pi ) rămân neschimbate pentru i ∈ {1, . . . , p} \ {k, k + 1}.
• Pentru ţestoasa ik mutată la poziţia k + 1, ı̂n locul lui (Pk ), are loc inegalitatea
cik > cik+1 >
k+1
X
gij
j=1
adică ik poate ţine ı̂n spate ţestoasele i1 , . . . , ik−1 , ik+1 .
• Pentru ţestoasa ik+1 mutată la poziţia k, ı̂n locul lui (Pk+1 ), are loc inegalitatea
cik+1 >
k+1
X
j=1
gij >
k
X
gij
j=1
adică ik+1 poate ţine ı̂n spate ţestoasele i1 , . . . , ik−1 , ik .
6
Prin urmare, dacă presupunem că datele de intrare hg1 , c1 i, hg2 , c2 i, . . . , hgn , cn i sunt sortate
astfel ı̂ncât c1 < c2 < . . . < cn , atunci numărul care ne interesează este valoarea maximă
a lui p pentru care există o subsecvenţă i1 , . . . , ip a lui 1, . . . , n care satisface condiţiile
(P1 ), . . . , (Pp ). p poate fi calculat astfel:
(a) Pentru fiecare 0 ≤ i ≤ n vom calcula recursiv perechile hL[i], G[i]i definite astfel:
I L[i] :=lungimea maximă a unei stive de ţestoase din mulţimea {1, . . . , i}.
Se observă că L[i] ≤ i pentru toţi i, L[1] = 1 şi L[i + 1] ≥ L[i] dacă i < n.
I G[i] este vectorul hG[i][1], . . . , G[i][2], . . . , G[i][L[i]]i ı̂n care G[i][j] este greutatea
minimă a unei stive de ţestoase de lungime j din mulţimea {1, . . . , i}.
(b) p = L[n].
Vom presupune că, iniţial, L[0] = 0, G[i][0] = 0, G[i][j] = ∞ pentru toţi 0 ≤ i ≤ n, 1 ≤ j ≤ n,
şi că S[i][j] este o stivă de ţestoase de lungime j şi greutate minimă din mulţimea {1, . . . , i}.
Atunci greutatea reală G[i][j] şi lungimea reală L[i] a stivei S[i][j] pot fi calculate recursiv,
pe baza următoarelor observaţii:
I Stiva S[i][j] trebuie să aibe ţestoasa i la bază dacă şi numai dacă:
(a) Ţestoasa i poate ţine pe spate o stivă S[i − 1][j − 1] de ţestoase de lungime j − 1
şi greutate minimă din mulţimea {1, . . . , i − 1}, adică ci ≥ G[i − 1][j − 1] + gi .
(b) Stiva cu ţestoasa i ţinând pe spate stiva S[i−1][j−1] este mai uşoară decât S[i−1][j],
adică G[i − 1][j − 1] + gi < G[i − 1][j].
Pentru situaţia ı̂n care j = L[i − 1] + 1, putem presupune că G[i − 1][j] = ∞.
În acest caz, G[i][j] := G[i − 1][j − 1] + gi .
Deasemenea, L[i] := L[i − 1] + 1 dacă şi numai dacă stiva S[i][L[i]] trebuie să aibe
ţestoasa i la bază, adică ci ≥ G[i − 1][L[i − 1]] + gi .
I Dacă stiva S[i][j] nu trebuie să aibe ţestoasa i la bază, putem presupune că S[i][j] =
S[i − 1][j]. În acest caz G[i][j] = G[i − 1][j].
Deci, pentru 1 ≤ i ≤ n şi 1 ≤ j ≤ L[i] avem
L[i − 1] + 1 dacă ci ≥ G[i − 1][L[i − 1]] + gi ,
L[i] :=
L[i − 1]
ı̂n caz contrar.
G[i][j] =

 G[i − 1, j] + gi

G[i − 1, j]
dacă ci ≥ G[i − 1, j − 1] + gi
şi G[i − 1][j − 1] + gi < G[i − 1][j],
dacă ci < G[i − 1, j − 1] + gi .
Graful de dependenţe de valori dintre vectorii G[i − 1] şi G[i] este
X[0]:
0 = G[i − 1][0]
X[1]:
G[i − 1][1]
G[i][1]
X[2]:
...
G[i − 1][2]
...
G[i][2]
...
X[L[i]]:
G[i − 1][L[i]]
7
G[i][0] = 0
G[i][L[i]]
Rezultă că, dacă X := G[i − 1] şi L := G[i − 1], atunci X şi L pot fi actualizaţi să reţină valorile
lui G[i] şi L[i] ı̂n felul următor
if ci ≥ X[L] + gi then L=L+1
for j = L downto 1 do
if ci ≥ X[j − 1] + gi and X[j − 1] + gi < X[j] then X[j] = X[j − 1] + gi
Observaţi că este important ca elementele lui X să fie recalculate pornind de la j=L până la
1: Dacă s-ar porni de la j=1 până la L, algoritmul ar fi incorect.
O implementare ı̂n C++ a acestei metode este ilustrată mai jos:
include <iostream>
#include <vector>
#include <limits>
#include <algorithm>
using namespace std;
struct Turtle {
int g; // weight
int c; // capacity
};
vector<Turtle> T;
bool comp(const Turtle &t1, const Turtle &t2) { return t1.c < t2.c; }
int main() {
Turtle t;
while (cin >> t.g >> t.c) {
if (t.c>=t.g)
// retinem doar testoasele semnificative
T.push_back(t);
sort(T.begin(),T.end(),comp);
}
int n = T.size();
int L = 0;
if (n==0) {
cout << L << endl;
return 0;
}
vector<int> X(n+1);
for (int i=1;i<=n;i++) X[i]=numeric_limits<int>::max();
X[0]=0;
for(int i=1;i<=n;i++) {
if (T[i-1].c>=X[L]+T[i-1].g) L++;
for (int j=L;j>=1;j--)
if ((T[i-1].c>=X[j-1]+T[i-1].g)&&(X[j-1]+T[i-1].g<X[j]))
X[j]=X[j-1]+T[i-1].g;
}
cout << L << endl;
return 0;
}
8
11.6.4 (Unidirectional TSP)
Given an m × n matrix of integers, you are to write a program that computes a path of
minimal weight from left to right across the matrix. A path starts anywhere in column 1
and consists of a sequence of steps terminating in column n. Each step consists of traveling
from column i to column i + 1 in an adjacent (horizontal or diagonal) row. The first and last
rows (rows 1 and m) of a matrix are considered adjacent; i.e., the matrix “wraps” so that it
represents a horizontal cylinder. Legal steps are illustrated below.
The weight of a path is the sum of the integers in each of the n cells of the matrix that are
visited.
The minimum paths through two slightly different 5 × 6 matrices are shown below. The
matrix values differ only in the bottom row. The path for the matrix on the right takes
advantage of the adjacency between the first and last rows.
Input
The input consists of a sequence of matrix specifications. Each matrix consists of the row
and column dimensions on a line, denoted m and n, respectively. This is followed by m · n
integers, appearing in row major order; i.e., the first n integers constitute the first row of the
matrix, the second n integers constitute the second row, and so on. The integers on a line
will be separated from other integers by one or more spaces. Note: integers are not restricted
to being positive. There will be one or more matrix specifications in an input file. Input is
terminated by end-of-file.
For each specification the number of rows will be between 1 and 10 inclusive; the number
of columns will be between 1 and 100 inclusive. No path’s weight will exceed integer values
representable using 30 bits.
Output
Two lines should be output for each matrix specification. The first line represents a minimalweight path, and the second line is the cost of this minimal path. The path consists of a
9
sequence of n integers (separated by one or more spaces) representing the rows that constitute
the minimal path. If there is more than one path of minimal weight, the lexicographically
smallest path should be output.
Răspuns. Fie A[1..m][1..n] matricea de ı̂ntregi m × n. Pentru a descrie faptul că linia 1
este adiacentă la linia m a matricii A, vom considera funcţia mod : {0, 1, . . . , m, m + 1} →
{1, 2, . . . , m}, cu mod(0) = m, mod(m + 1) = 1 şi mod(i) = i pentru 1 ≤ i ≤ m.
Pentru fiecare 1 ≤ j ≤ n, fie dp[j] vectorul m-dimensional
dp[j] = hdp[1][j], dp[2][j], . . . , dp[m][j]i
unde dp[i][j] este costul minim al unei căi care porneşte de la poziţia (i, j) şi se termină ı̂n
coloana n, efectuând paşi legali.
Costul unei căi minime este min{dp[1][j] | 1 ≤ j ≤ n}, iar calea lexicografică minimă
hi1 , i2 , . . . , im i care are acest cost se poate determina astfel:
• i1 = min{i | dp[i][1] = min{dp[1][1], dp[2][1], . . . , dp[m][1]}}
• Pentru orice 1 < j ≤ n, ij = min{i | i ∈ Aj şi dp[ij ][j] = min{dp[k][j] | k ∈ Aj }} unde
Aj = {mod(ij−1 − 1), ij−1 , mod(ij−1 + 1)}.
Calculul vectorilor dp[j] (1 ≤ j ≤ n) poate decurge astfel:
• dp[n] = hA[1][n], A[2][n], . . . , A[m][n]i.
• Pentru 1 ≤ j < n şi 1 ≤ i ≤ n:
dp[i][j] = A[i][j] + min{dp[mod(i − 1)][j + 1], dp[i][j + 1], dp[mod(i + 1)][j + 1]}
Implementare ı̂n C++:
#include <iostream>
using namespace std;
int M, N;
int A[11][101], dp[11][101];
inline int min(int a, int b, int c) {return (a<b)?(a<c?a:c):(b<c?b:c);}
inline int mod(int x) {return (x==0)?M:(x==M+1?1:x);}
void reportSolution();
int main() {
while (cin >> M >> N) {
for (int i=1;i<=M;i++)
for (int j=1;j<=N;j++) cin >> A[i][j];
for (int i=1;i<=M;i++) dp[i][N]=A[i][N];
for (int j=N-1;j>=1;j--)
for (int i=1;i<=M;i++)
dp[i][j]=A[i][j]+min(dp[mod(i-1)][j+1],dp[i][j+1],dp[mod(i+1)][j+1]);
reportSolution();
}
return 0;
}
10
void reportSolution() {
int crt=M,k,ijnew;
for (int i=M-1;i>=1;i--)
if (dp[i][1]<=dp[crt][1]) crt=i;
int ij=crt;
cout<<ij;
for (int j=2;j<=N;j++) {
k=mod(ij+1);
ijnew=((dp[k][j]<dp[ij][j])
||((dp[k][j]==dp[ij][j])&&(k<ij)))?k:ij;
k=mod(ij-1);
ij=((dp[k][j]<dp[ijnew][j])
||((dp[k][j]==dp[ijnew][j])&&(k<ijnew)))?k:ijnew;
cout<<’ ’<<ij;
}
cout << endl<<dp[crt][1]<<endl;
}
11
11.6.5 (Cutting sticks)
You have to cut a wood stick into several pieces. The most affordable company, Analog
Cutting Machinery (ACM), charges money according to the length of the stick being cut.
Their cutting saw allows them to make only one cut at a time.
It is easy to see that different cutting orders can lead to different prices. For example,
consider a stick of length 10 m that has to be cut at 2, 4, and 7 m from one end. There
are several choices. One can cut first at 2, then at 4, then at 7. This leads to a price of
10 + 8 + 6 = 24 because the first stick was of 10 m, the resulting stick of 8 m, and the last
one of 6 m. Another choice could cut at 4, then at 2, then at 7. This would lead to a price
of 10 + 4 + 6 = 20, which is better for us.
Your boss demands that you write a program to find the minimum possible cutting cost for
any given stick.
Input
The input will consist of several input cases. The first line of each test case will contain a
positive number l that represents the length of the stick to be cut. You can assume l < 1, 000.
The next line will contain the number n (n < 50) of cuts to be made. The next line consists
of n positive numbers ci (0 < ci < l) representing the places where the cuts must be made,
given in strictly increasing order.
An input case with l = 0 represents the end of input.
Output
Print the cost of the minimum cost solution to cut each stick in the format shown below.
Sample Input
100
3
25 50 75
10
4
4 5 7 8
0
Sample Output
The minimum cutting is 200.
The minimum cutting is 22.
Răspuns
Fie c vectorul de n + 2 valori h0, c1 , c2 , . . . , cn−1 , cn , li, şi mc(i, j) costul minim de tăiat ı̂n
bucăţi segmentul de băţ dintre tăieturile c[i] şi c[j] (0 ≤ i < j ≤ n + 1). Există C(n + 2, 2) =
12
(n+1)(n+2)/2 de astfel de valori. Deoarece n < 50, avem de calculat cel mult 50·51/2 = 1275
valori. Formula de calcul recursiv a acestor valori este
0
dacă j − i = 1
mc(i, j) :=
c[j] − c[i] + min{mc(i, k) + mc(k, j) | i < k < j} dacă j − i > 1.
O implementare ı̂n C++ a acestei idei este dată mai jos.
#include <iostream>
#include <vector>
using namespace std;
vector<int> dp(1275);
int c[51], N;
int mc(int i, int j) {
if (j-i==1) return 0;
int& dpij = dp[N*i+j];
if (dpij>0) return dpij;
int m = 1000, tmp;
for (int k=i+1;k<j;k++) {
tmp = mc(i,k)+mc(k,j);
if (tmp<m) m=tmp;
}
dpij = m+c[j]-c[i];
return dpij;
}
int main() {
int l,n;
while(true) {
cin >> l;
if(l==0) break;
cin >> n;
c[0]=0; c[n+1]=l;
for (int i=1;i<=n;i++) cin >> c[i];
for (int i=0;i<dp.size();i++) dp[i]=0;
N=n+2;
cout << mc(0,n+1) << endl;
}
return 0;
}
13
11.6.6 (Ferry Loading)
Ferries are used to transport cars across rivers and other bodies of water. Typically, ferries
are wide enough to support two lanes of cars throughout their length. The two lanes of cars
drive onto the ferry from one end, the ferry crosses the water, and the cars exit from the
other end of the ferry.
The cars waiting to board the ferry form a single queue, and the operator directs each car in
turn to drive onto the port (left) or starboard (right) lane of the ferry so as to balance the
load. Each car in the queue has a different length, which the operator estimates by inspecting
the queue. Based on this inspection, the operator decides which side of the ferry each car
should board, and boards as many cars as possible from the queue, subject to the length
limit of the ferry.Write a program that will tell the operator which car to load on which side
so as to maximize the number of cars loaded.
Input
The input begins with a single positive integer on a line by itself indicating the number of
test cases, followed by a blank line.
The first line of each test case contains a single integer between 1 and 100: the length of the
ferry (in meters). For each car in the queue there is an additional line of input specifying
the length of the car in cm, an integer between 100 and 3,000 inclusive. A final line of input
contains the integer 0. The cars must be loaded in order, subject to the constraint that the
total length of cars on either side does not exceed the length of the ferry. As many cars
should be loaded as possible, starting with the first car in the queue and loading cars in
order until the next car cannot be loaded.
There is a blank line between each two consecutive inputs.
Output
For each test case, the first line of output should give the number of cars that can be loaded
onto the ferry. For each car that can be loaded onto the ferry, in the order the cars appear
in the input, output a line containing “port” if the car is to be directed to the port side and
“starboard” if the car is to be directed to the starboard side. If several arrangements of
cars meet the criteria above, any one will do.
The output of two consecutive cases will be separated by a blank line.
Răspuns. Pentru fiecare caz, notăm cu m = hm[0], . . . , m[n − 1]i vectorul lungimilor maşinilor
care aşteaptă să fie ı̂mbarcate pe ferry, ı̂n ordinea citirii lor de la input, şi cu L lungimea ı̂n
cm a ferry-ului. Ştim că 100 ≤ L ≤ 10, 000 şi că 100 ≤ m[i] ≤ 3, 000 pentru 0 ≤ i < n.
Fie nmax numărul maxim de maşini care pot fi ı̂mbarcate pe ferry. Programul trebuie să
raporteze valoarea lui nmax şi, pentru fiecare din primele k maşini, partea unde este ı̂mbarcată:
“port” sau “starboard”.
14
Pk
Cum putem calcula nmax? Primele k maşini ocupă lungimea totală i=1 m[i − 1], care
trebuie să fie cel mult 2 · L, deci nmax ≤ M unde
(
)!
k
X
M = max {0} ∪ i | 1 ≤ i ≤ n şi
m[i − 1] ≤ 2 · L
i=1
Mai departe putem gândi astfel: Să presupunem că am ı̂mbarcat primele i−1 maşini pe ferry,
şi că au mai rămas s1 cm liberi pe banda “port” şi s2 cm liberi pe banda “starboard”. Fie
dp[s1 , s2 , i] numărul maxim de maşini rămase care mai pot fi ı̂mbarcate pe ferry. Este evident
că nmax = dp[L, L, 0].
Cum putem calcula dp[s1 , s2 , i]? Maşina care urmează să fie ı̂mbarcată are lungimea m[i].
Distingem 2 cazuri:
(a) Dacă s1 < m[i] şi s2 < m[i], maşina i nu poate fi ı̂mbarcată, deci
dp[s1 , s2 , i] = 0.
(b) Dacă s1 < m[i] şi s2 ≥ m[i], maşina i trebuie pusă pe banda 2, deci
dp[s1 , s2 , i] = 1 + dp(s1 , s2 − m[i], i + 1).
(c) Dacă s1 ≥ m[i] şi s2 < m[i], maşina i trebuie pusă pe banda 1, deci
dp[s1 , s2 , i] = 1 + dp(s1 − m[i], s2 , i + 1).
(d) Altfel, s1 ≥ m[i] şi s2 ≥ m[i]. În acest caz
dp[s1 , s2 , i] = 1 + max(dp[s1 − m[i], s2 , i + 1], dp[s1 , s2 − m[i], i + 1]).
Numărul maxim de valori care trebuie calculate pentru dp se poate estima ı̂n felul următor:
• Lungimea minimă a unei maşini este 100 cm, iar lungimea ferry-ului este L ≤ 10, 000 cm,
deci numărul maxim de maşini ce pot fi ı̂mbarcate este b10, 000/100c = 100. Rezultă că
i ∈ {0, . . . , k} ia cel mult 101 valori.
• 0 ≤ s1 , s2 ≤ 10, 000, deci s1 , s2 pot lua cel mult 10001 valori.
Deci, numărul maxim de valori pentru dp este 100012 · 101 ≈ 1010 , care este prea mare!
O observaţie foartePutilă este că s2 se poate calcula dacă ştim s1 şi i: maşinile 0, 1, . . . , i − 1
i−1
acoperă lungimea j=0 m[j] = 2 · L − s1 − s2 , deci
s2 = 2 · L − s1 −
i−1
X
m[j].
j=0
Deci este suficient să reţinem valorile lui dp pentru indecşii 0 ≤ s1 ≤ 10, 000 şi 0 ≤ i ≤ 100,
adică cel mult 10001 · 101 valori, care este un număr de mărime rezonabilă.
Benzile pe care pot fi plasate primele nmax maşini se pot determina astfel: Dacă s1 şi s2 sunt
porţiunile libere rămase din benzile port si starboard, iar maşina următoare ce trebuie pusă
pe ferry este i cu lungimea m[i], atunci:
I Dacă m[i] ≤ s1 şi dp[i][s1 ] == 1 + dp[i + 1, s1 − m[i]], punem maşina i pe “starboard”.
I În caz contrar, punem maşina i pe “port”.
15
#include <iostream>
#include <vector>
using namespace std;
int L, int dp[101][10001];
vector<int> m, msum;
int dpSolve(int i,int s1) {
if (i>=m.size()) return 0;
int s2 = 2*L-s1-msum[i],x=m[i];
if (dp[i][s1]>=0) return dp[i][s1];
if (x>s1) dp[i][s1]=(x>s2?0:1+dpSolve(i+1,s1));
else dp[i][s1]=1+(x>s2?
dpSolve(i+1,s1-x):max(dpSolve(i+1,s1),dpSolve(i+1,s1-x)));
return dp[i][s1];
}
void initSolve() {
m.clear();
msum.clear();
msum.push_back(0);
for (int i=0;i<=100;i++)
for (int j=0;j<=10000;j++) dp[i][j]=-1;
}
void showSolution(int i,int s1) {
if (dp[i][s1]==0) return;
if (s1>=m[i] && dp[i+1][s1-m[i]]==dp[i][s1]-1) {
cout << "port" << endl;
showSolution(i+1,s1-m[i]);
} else {
cout << "starboard" << endl;
showSolution(i+1,s1);
}
}
int main() {
int N,x,sum;
cin >> N; // read number of test cases
while (N--) {
cin >> L;
L*=100;
sum=0;
initSolve();
while (true) {
cin >> x;
if(x==0) break;
sum+=x;
m.push_back(x); msum.push_back(sum);
};
cout << dpSolve(0,L) << endl;
showSolution(0,L);
}
return 0;
}
16
11.6.7 (Chopsticks) In China, people use pairs of chopsticks to eat food, but Mr. L is a bit different.
He uses a set of three chopsticks, one pair plus an extra; a long chopstick to get large items
by stabbing the food. The length of the two shorter, standard chopsticks should be as close
as possible, but the length of the extra one is not important so long as it is the longest. For
a set of chopsticks with lengths A, B, C (A ≤ B ≤ C), the function (A − B)2 defines the
“badness” of the set.
Mr. L has invited K people to his birthday party, and he is eager to introduce his way of
using chopsticks. He must prepare K + 8 sets of chopsticks (for himself, his wife, his little
son, little daughter, his mother, father, mother-in-law, father-in-law, and K other guests).
But Mr. L’s chopsticks are of many different lengths! Given these lengths, he must find a
way of composing the K + 8 sets such that the total badness of the sets is minimized.
Input
The first line in the input contains a single integer T indicating the number of test cases
(1 ≤ T ≤ 20). Each test case begins with two integers K and N (0 ≤ K ≤ 1, 000, 3 K + 24 ≤
N ≤ 5, 000) giving the number of guests and the number of chopsticks.
Then follow N positive integers Li , in non-decreasing order, indicating the lengths of the
chopsticks (1 ≤ Li ≤ 32, 000).
Output
For each test case in the input, print a line containing the minimal total badness of all the
sets.
Răspuns. Fie L0 , L1 , . . . , LN −1 secvenţa nedescrescătoare de ı̂ntregi pozitivi care indică
lungimile beţelor, şi P = {P1 , P2 , . . . , PK+8 } o mulţime de seturi de beţe alese dintre cele N ,
care are abaterea totală minimă. Dacă P1 , P2 ∈ P, P1 = (A1 , B1 , C1 ), P2 = (A2 , B2 , C2 ) cu
A1 ≤ A2 atunci este necesar ca B1 ≤ A2 deoarece, ı̂n caz contrar am obţine contradicţii:
(a) Dacă A1 ≤ A2 < B1 ≤ B2 atunci, dacă ı̂nlocuim P1 , P2 cu
P10 = (A1 , A2 , C1 ), P20 = (B1 , B2 , C2 )
obţinem o abatere totală mai mică fiindcă
(A2 − A1 )2 + (B2 − B1 )2 < (B1 − A1 )2 + (B2 − A2 )2
(b) Celălalt caz posibil este A1 ≤ A2 ≤ B2 ≤ B1 cu B1 > A2 . Dacă ı̂nlocuim P1 , P2 cu
P10 = (B2 , B1 , C1 ), P10 (A1 , A2 , C2 )
obţinem o abatere totală mai mică fiindcă
(B1 − B2 )2 + (A2 − A1 )2 < ((B1 − B2 ) + (B2 − A2 ) + (A2 − A1 ))2
= (B1 − A1 )2 ≤ (B1 − A1 )2 + (B2 − A2 )2 .
17
Deasemenea, putem presupune că, dacă P = (A, B, C) este un set ales de beţe, atunci A, B
sunt valori consecutive de lungimi, fiindcă ı̂n caz contrar am obţine contradicţii: Dacă ar
exista un băţ de lungime L cu A < L < B, am fi ı̂n una din situaţiile următoare:
(a) Există un set de beţe P 0 = (A0 , B 0 , C 0 ) cu A0 ≤ B 0 ≤ A şi C 0 = L. În acest caz putem
ı̂nlocui P 0 cu (A0 , B 0 , C) şi P cu (A, L, B) şi obţinem o abatere totală mai mică fiindcă
B 0 < C, L < B şi (B 0 − A0 )2 + (L − A)2 < (B 0 − A0 )2 + (B − A)2 .
(b) L nu face parte din nici un alt set de beţe. În acest caz ı̂nlocuim P cu setul (A, L, C) şi
obţinem o abatere totală mai mică fiindcă L < C şi (L − A)2 < (B − A)2 .
Deci putem presupune că seturile de beţe alese formează o secvenţă
(LaK+8 , LaK+8 +1 , LbK+8 ), (LaK+7 , LaK+7 +1 , Lb1 ), . . . , (La1 , La1 +1 , Lb1 )
cu aj +1 < bj pentru 1 ≤ j ≤ K +8, şi aj+1 < aj −1 pentru 1 ≤ j < K +8. Numerele distincte
SK+8
bj ∈ {0, 1 . . . , N − 1} \ k=1 {ak , ak + 1} cu bj > aj + 1 pentru toţi j ∈ {1, 2, . . . , K + 8}
există dacă şi numai dacă au loc inegalităţile
I aj+1 < aj − 1 pentru 1 ≤ j < K + 8,
I Pentru 1 ≤ j ≤ K + 8, există cel puţin 2 · (K + 8 − j) numere mai mici decât aj , şi cel
puţin 3 · j numere ı̂ntre aj şi N − 1. Altfel spus, 2 · (K + 8 − j) ≤ aj ≤ N − 3 · j.
nP
o
K+8
2
Prin urmare, avem de calculat min
k=1 (Lai +1 − Lai ) | haK+8 , aK+7 , . . . , a1 i ∈ S unde
S este mulţimea subsecvenţelor haK+8 , aK+7 , . . . , a1 i lui h0, 1, . . . , N −1i care satisfac condiţiile
(a) aj+1 < aj − 1 pentru toţi 1 ≤ j < K + 8, şi
(b) 2 · (K + 8 − j) ≤ aj ≤ N − 3 · j pentru toţi 1 ≤ j ≤ K + 8.
Pentru 0 ≤ i ≤ N − 3 definim dp[i] = hdp[i][0], dp[i][1], . . . , dp[i][K + 8]i, unde
(
dp[i][j] := min {∞} ∪
j
X
)!
2
(Lak +1 − Lak ) | haj , . . . , a2 , a1 i ∈ Sj
k=1
unde Sj este mulţimea subsecvenţelor haj , . . . , a2 , a1 i lui hi, i + 1, . . . , N − 3i ce satisfac
condiţiile
(a) ak+1 < ak − 1 pentru toţi 1 ≤ k < j, şi
(b) 2 · (K + 8 − k) ≤ ak ≤ N − 3 · k pentru toţi j ≤ k ≤ K + 8.
Atunci numărul căutat este dp[0][K + 8]. Pentru a-l calcula, observăm că dp[i][0] = 0 pentru
toţi 0 ≤ i ≤ N − 3 şi
(LN −2 − LN −3 )2 dacă j = 1,
dp[N − 3][j] :=
∞
dacă 1 < j ≤ K + 8
dp[N − 4][j] :=
min((LN −2 − LN −3 )2 , (LN −3 − LN −4 )2 ) dacă j = 1,
∞
dacă 1 < j ≤ K + 8
Dacă 1 ≤ i ≤ N − 4 atunci dp[i − 1][j] cu 1 ≤ j ≤ K + 8 poate fi calculat astfel:
18
(a) Dacă aj ı̂n haj , . . . , a2 , a1 i ∈ Sj poate fi i − 1 atunci
dp[i − 1][j] = min((Li − Li−1 )2 + dp[i + 1][j − 1], dp[i][j])
Acest caz are loc când 2 · (K + 8 − j) ≤ i − 1 ≤ N − 3 · j.
(b) În caz contrar, dp[i − 1][j] = dp[i][j].
Implementare ı̂n C++:
#include <iostream>
#include <vector>
#include <limits>
using namespace std;
const double infty = numeric_limits<int>::max();
int K, N;
vector<int> L;
double dp[3][1009];
inline int min(int x, int y) { return (x<y?x:y); }
int solve() {
dp[0][0]=dp[1][0]=dp[2][0]=0;
for (int j=1;j<=K+8;j++) dp[0][j]=dp[1][j]=dp[2][j]=infty;
int x=L[N-2]-L[N-3];
dp[N%3][1]=x*x;
x=L[N-3]-L[N-4]; x=x*x;
dp[(N-4)%3][1]=min(dp[N%3][1],x);
for (int i=N-4;i>=1;i--)
for (int j=1;j<=K+8;j++)
if (2*(K+8-j)<=i-1
&& i-1<=N-3*j
&& dp[(i+1)%3][j-1]<infty) {
x=L[i]-L[i-1];x=x*x+dp[(i+1)%3][j-1];
dp[(i-1)%3][j]=min(x,dp[i%3][j]);
} else dp[(i-1)%3][j]=dp[i%3][j];
return dp[0][K+8];
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> K >> N;
L.resize(N);
for(int j=0;j<N;j++) cin>>L[j];
cout << solve()<<endl;
}
}
19
11.6.8 (Adventures in Moving)
You are considering renting a moving truck to help you move from Waterloo to the big city.
Gas prices being so high these days, you want to know how much the gas for this beast will
set you back. The truck consumes a full liter of gas for each kilometer it travels. It has a
200-liter gas tank. When you rent the truck in Waterloo, the tank is half-full. When you
return it in the big city, the tank must be at least half-full, or you’ll get gouged even more
for gas by the rental company. You would like to spend as little as possible on gas, but you
don’t want to run out along the way.
Input
The input begins with a single positive integer on a line by itself indicating the number of test
cases, followed by a blank line. Each test case is composed only of integers. The first integer
is the distance in kilometers from Waterloo to the big city, at most 10,000. Next comes a
set of up to 100 gas station specifications, describing all the gas stations along your route, in
non-decreasing order by distance. Each specification consists of the distance in kilometers of
the gas station from Waterloo, and the price of a liter of gas at the gas station, in tenths of
a cent, at most 2,000.
There is a blank line between each two consecutive inputs.
Output
For each test case, output the minimum amount of money that you can spend on gas to get
from Waterloo to the big city. If it is not possible to get from Waterloo to the big city subject
to the constraints above, print “Impossible”. The output of each two consecutive cases will
be separated by a blank line.
Răspuns: Să presupunem că S0 , S1 , . . . , Sn sunt staţiile de gaz, la distanţele d0 , d1 , . . . , dn
faţă de Waterloo, cu preţurile/litru p0 , p1 , . . . , pn . Fie d distanţa de la Waterloo la big city.
Waterloo
p0
S0
p1
S1
p2
S2
pn−1
Sn−1
pn
Sn
d0
d1
d2
dn−1
dn
d
big city
Pentru a rezolva această problemă vom calcula cu programare dinamică vectorii
dp[i] = hdp[i][0], dp[i][1], . . . , dp[i][200]i (0 ≤ i ≤ n)
unde dp[i][j] este preţul minim care trebuie dat pe gaz pentru a ajunge la staţia Si cu j litri
rămaşi ı̂n rezervor. Pentru situaţiile ı̂n care nu se poate ajunge la staţia Si cu j litri rămaşi
ı̂n rezervor, vom defini dp[i][j] := MAXINT, unde MAXINT este valoarea maximă a unui ı̂ntreg.
Fie minCost costul minim care ne interesează. După ce am calculat vectorul dp[n], putem
raţiona astfel:
20
Rezervorul trebuie să conţină 100 litri când se ajunge ı̂n big city, iar d − dn litri sunt
consumaţi pe distanţa de la Sn la big city. Deci, la plecare din Sn rezervorul trebuie să
conţină 100 + (d − dn ) litri, ceea ce este posibil doar dacă 100 + (d − dn ) ≤ 200. Aşadar
• Dacă 100 + d − dn > 200 atunci minCost := MAXINT (ceea ce indică “IMPOSSIBLE”).
• Dacă 100 + d − dn ≤ 200 atunci şoferul trebuie să plece din Sn cu 100 + d − dn litri
ı̂n rezervor. Dacă şoferul ajunge ı̂n Sn cu k litri atunci trebuie ca
– k ≤ 100 + d − dn .
– să cheltuie minim, adică d[n][k] lei pentru a ajunge ı̂n sta tia Sn ,
– să mai cumpere 100 + d − dn − k litri de benzină din Sn cu preţul de pn lei/litru.
Adică, trebuie să mai plătească pn · (100 + d − dn − k) lei.
Deci, ı̂n total l-ar costa d[n][k] + pn · (100 + d − dn − k) lei.
Aşadar
minCost := min({MAXINT} ∪ {dp[n][k] + pn · (100 + d − dn − k) | k ∈ An })
unde An = {k | 0 ≤ k ≤ 100 + d − dn şi dp[n][k] 6= MAXINT}.
Pentru calculul dinamic al lui dp[n], observăm că
I dp[0] are toate elementele MAXINT, cu excepţia cazului când 100 − d0 ≥ 0, iar ı̂n acest
caz dp[0][100 − d0 ] := 0.
I Pentru 1 ≤ i ≤ n putem calcula dp[i] din dp[i − 1] calculând dp[i][j] pentru 0 ≤ j ≤ 200
ı̂n felul următor:
– De la Si−1 la Si se consumă di − di−1 litri iar j este numărul de litri de benzină
cu care ajunge ı̂n Si . Deci maşina trebuie să plece din Si−1 cu j + di − di−1 litri.
Acest lucru este posibil doar dacă şoferul ajunge ı̂n Si−1 cu k litri ı̂n rezervor, iar
şoferul mai cumpără j + di − di−1 − k litri de benzină din Si−1 . În această situaţie,
costul minim este dp[i − 1][k] + pi−1 · (j + di − di−1 − k). Rezultă că
dp[i][j] := min({MAXINT} ∪ {dp[i − 1][k] + pi−1 · (j + di − di−1 − k) | k ∈ Ai−1 })
unde Ai−1 = {k | 0 ≤ k ≤ min(j + di − di−1 , 200) şi dp[i − 1][k] 6= MAXINT}.
Dacă dp[i] are toate elementele egale cu MAXINT (adică nu se poate ajunge ı̂n Si ) atunci
dp[k] = dp[i] pentru toţi i < k ≤ n (nu se poate ajunge nici in Sk ) şi minCost = MAXINT
(ceea ce indică “IMPOSSIBLE”)
O implementare in C++ a acestei metode este
#include
#include
#include
#include
<iostream>
<sstream>
<vector>
<limits>
using namespace std;
const int MAXINT=numeric_limits<int>::max();
struct Station { int d, p; };
vector<Station> v;
vector<int> dp(201), dpNext(201);
21
int getBestCost(int d) {
int n=v.size()-1, inc=100-v[0].d;
if (inc<0) return MAXINT;
for (int j=0;j<=200;j++) dp[j]=MAXINT;
dp[inc]=0;
bool b;
for (int i=1;i<=n;i++) {
inc=v[i].d-v[i-1].d;
b=true;
for (int j=0;j<=200;j++) {
dpNext[j]=MAXINT;
if (j+inc<=200)
for (int k=0;k<=j+inc;k++) {
if (dp[k] == MAXINT) continue;
if (dpNext[j]>dp[k]+v[i-1].p*(j+inc-k)) {
b=false;
dpNext[j]=dp[k]+v[i-1].p*(j+inc-k);
}
}
}
if (b) return MAXINT;
dp=dpNext;
}
inc = d-v[n].d;
int bestCost=MAXINT;
if (100+inc<=200)
for (int k=0;k<=100+inc;k++) {
if (dp[k] == MAXINT) continue;
if (bestCost>dp[k]+v[n].p*(100+inc-k))
bestCost=dp[k]+v[n].p*(100+inc-k);
}
return bestCost;
}
int main() {
int N, minCost, d;
string l;
getline(cin,l);
stringstream(l) >> N;
getline(cin,l);
Station s;
while (N--) {
v.clear();
getline(cin,l);
stringstream(l) >> d;
while (true) { // read next test case
getline(cin,l);
if(l=="") break;
stringstream(l) >> s.d >> s.p;
v.push_back(s);
};
22
minCost = getBestCost(d);
if (minCost == MAXINT)
cout << "Impossible" << endl;
else
cout << minCost << endl;
}
}
23