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
© Copyright 2024 Paperzz