📜 ⬆️ ⬇️

Dynamic programming in olympiad problems

Hello!

Recently I solved problems from the Timus Online Judge archive and came across a section called dynamic programming tasks . Tasks of this type make me particularly interested, because often this approach ensures the speed and elegance of the solution. What is dynamic programming?

Dynamic programming is an approach to problem solving, in which there is a division into subtasks that are “simpler” in comparison with the original one. The word "dynamic" is close in meaning to "inductive": it is assumed that the answer is known for some value kand I want to find the answer for k+1. In mathematics, this is called an inductive transition, and constitutes the basic idea of ​​dynamic programming.
')

Simple examples


The most striking and illustrative task is the task of computing nth Fibonacci sequence number. It is known that the sequence has the following properties:

F0=F1=1, Fn=Fn1+Fn2.


This immediately implies the recurrence formula:

int Fibonacci(int n) { if(n == 1 || n == 2) return 1; return Fibonacci(n-1) + Fibonacci(n-2); } 

If the recursion searches for the number “from the end”, then the following method sequentially calculates all the numbers between 0and n:

 int dpFibonacci(int n) { int prev1 = 1; int prev2 = 1; int curr = 0; for(int j = 2; j < n; j++) { curr = prev1 + prev2; prev1 = prev2; prev2 = curr; } return curr; } 

It is clear that with sufficiently large nThis algorithm is much faster: it does not calculate intermediate values ​​several times. Consider a slightly more complex example.

Example 1. You walk up the toll ladder. To step on istep you need to pay aiof coins. You can step to the next step or jump over one. Task: to pass nsteps and spend as little coins as possible.

It is clear that stepping over each step, we minimize the number of "payments", but we can run into a very expensive step that we would like to avoid. Create an array of values din which on jThe first place will be the (minimum) number of coins that must be spent to get to jth steps. It is immediately clear that d1=a1, d2=a2. And then we will take at least two previous steps and add the cost of the step itself:

di= min left(di1,di2 right)+ai.



We change the conditions of the problem a little: suppose that at some steps you can receive coins (this will mean that ak<0). What needs to be changed in the algorithm so that it gives the correct result?

Decision
It is necessary to change only the "beginning" of our dynamics. If the first ladder does not bring us coins, then it is advisable to jump over it, however, if a1<0it is better to step on and collect your coins. So, d2= min left(0,d1 right)+a2.

Consider another example that uses the "two-dimensional" dynamics.

Example 2. The maze has n timesmrooms, each of which is gold (in a cage (i,j)lies aijgold). The task is to determine what the maximum amount of gold can be collected with the optimal route from the point (0,0)exactly (n,m)if you can go either down or to the right.

So, we want to know the optimal route to the cell. (i,j). Here we can get from two cells - (i1,j)and (i,j1). Given that the optimal routes for these two cells are known (they are stored in a table d), then the answer is for the cell (i,j)is obtained as follows:

dij= max left(di1j,dij1 right)+aij.


This is another classic dynamic programming problem, modifications of which are quite often encountered in problems of sports programming. More similar problem is explained here .

More challenging tasks

If desired, the dynamic approach can be screwed anywhere. Consider a task from the Timus Online Judge archive.

The mathematical formulation of the problem is as follows: it is required to find the minimum number of terms required to decompose a given number into full squares.

As before, suppose we know the answers for all numbers. k1that are stored in an array dand we would like to find dk.

Take this number kand analyze what could be the situation:

  1. kis a complete square. In this case dk=1.
  2. Perhaps the previous number k1was a complete square. Then dk=dk1+1.

In general, the option of adding one to the previous one does not seem so bad.

We proceed as follows: we will seek expansion k=q2+ssuch that

dq2+ds<dk1+1.

Because q2- full square, then dq2=1and

ds<dk1,

that is, we found a partition that is simply better than dk1+1and the answer in this case will be

dk=ds+dq2=ds+1.



Sample Java code that implements this algorithm:
 for(int k = 1; k <= n; k++) { int best = d[k - 1] + 1; //      int q = 1; while(k - q*q >= 0) { // k = q*q + s if(k - q*q == 0) { // k -   best = 1; break; } else if(d[k - q*q] < best) best = d[k - q*q] + 1; q++; } d[k] = best; } 


Consider the following problem . The goal is to build a ladder from Ncubes by the rules:

  1. the staircase has at least two steps;
  2. a staircase cannot have two identical steps;
  3. steps of a ladder go in ascending order (that is, the next one is greater than the previous one).

This time we will build a two-dimensional dynamics. Create a tab din which position (i,j)will lie the number of stairs consisting of icubes whose height does not exceed j. If it works, then the answer to our problem will be the amount

 sum limitsj=1ndnj.


So, we will solve the problem of finding the number of stairs composed of icubes that have height j. The picture shows the stairs that fall into d74:

Since we know all the stairs, which consist of a smaller number of cubes, we “split” from the stairs (i,j)right column. The result is a staircase c ijcubes. Example for i=9, j=4:

But for such stairs, the result is already known, so we will sort through all such stairs in a cycle kand add up all the results. In this way,

dij= sum limitsk=1j1dijk.


Now we will go through the height of the stairs:

dij= sum limitsk=1j1dijk, j= overline1,i.

Finally, changing ifrom 1before nwill get the answer.

Important : in the process of building our matrix, you need to consider dii=1, because otherwise, some types of ladders will be “lost” (during “splitting off”), but it goes without saying that such a ladder does not satisfy the conditions of the task, therefore the answer will be the number dnn1.

Sample Java code that implements this algorithm:

 dp = new long[n + 1][n+1]; d[1][1] = 1; d[2][1] = 0; d[2][2] = 1; for(int i = 3; i < n + 1; i++) { for(int j = 2; j <i; j++) { long cnt = 0; for(int k = 1; k < j; k++) { cnt += d[i - j][k]; } d[i][j] = cnt; } d[i][i] = 1; //    } long answer = 0L; for(int i = 0; i <= n; i++) { answer += d[n][i]; } answer--; //     


The next problem is solved using a one-dimensional array.

So what we have. The first ent knows 2 words. Each ent teaches all the words that he knows himself, two ents: young and old. In turn, the young were taught as many more words as he already knows, and the old were taught only one word. Need to know how many ents know exactly Kwords (you must print the number of these ents modulo P).

The solution is quite simple. Create an array din which on ith place we will store the number of ents (modulo Pwho know iof words. It all starts with the first ent who knows two words, so d2=1. And then everything is simple:


It remains to understand the calculation of the module. In order not to store huge numbers, we will immediately memorize all the values ​​in the module.

Sample Java code that implements this algorithm:
 int[] d = new int[K + 1]; if(K >= 2) d[2] = 1; if(P != 1) { for(int i = 3; i <= K; i++) { if(i % 2 != 0) { d[i] = d[i - 1]; } else { d[i] = ((d[i/2] % P) + d[i - 1] % P) % P; } } } else d[K] = 0; 


Resources used:

  1. Timus Online Judge;
  2. A little bit about dynamic programming;
  3. Comparison properties modulo.

Source: https://habr.com/ru/post/418867/


All Articles