Very complete linear DP and memory search handouts

Basic concepts

We contacted the previous lessons among the most basic of dynamic programming.
Dynamic Programming most important thing is to find a state and a state transition equation.
In addition, dynamic programming problem analysis there are some important properties, such as: overlapping sub-problems, optimal substructure, no after-effects and so on.

Optimal substructure concepts:
1) If the optimal solution of the problem contains a sub optimal solution of the problem, the problem with optimal substructure. When a problem with optimal substructure, we might want to use dynamic programming (greedy strategy is also likely to apply).

2) the search for optimal substructure, can follow a common pattern:

  • A solution to the problem may be an option. For example, the assembly station selection.
  • Assumptions about a given problem, it is known that can result in the selection of the optimal solution. You do not care about how to determine this choice, assuming he is known.
  • After this selection is known to identify those sub-problems ensue, and how best to describe the problem space of the child obtained.
  • Using a "cut and paste" technique to demonstrate an optimal solution to the problem, the solution of the problem of the use of child itself must be optimal.

3) optimal substructure in two ways changes in the problem domain:

  • How many sub-problems to be used in an optimal solution to the original problem, and in
  • How many choose when to use those sub-problems in determining an optimal solution in

In the assembly line scheduling problem, an optimal solution using only one sub-problems, however, to determine an optimal solution we must consider two options.

4) the difference between dynamic programming and greedy algorithms

  • Dynamic programming in a bottom-up manner using optimal substructure. That is, first find the optimal solution of the problem child, the child solve the problem, then find an optimal solution to the problem. The problem of finding an optimal solution need to make a choice in the sub-question, choose which one to use to solve the problem. Solutions for the cost is usually the cost of sub-problems, plus the cost to bring their own choice.
  • In the greedy algorithm is based on a top-down manner using optimal substructure. Greedy algorithm would do first choose how, at the time appears to be the best choice, then a fruit in solving the problem, rather than a sub-optimal solution is now looking for the problem, and then make choices.

Overlapping sub-problems concept:
for the most dynamic optimization problem solver must have a second element is the space sub-problems to be "very small", a recursive algorithm for solving the original problem can be repeatedly solution that is used for the same child problem, but not always in a new sub-problems.
For example: Status \ (I \) Solving and status \ (i-1 \) related to the state of \ (i-1 \) Solving and status \ (i-2 \) relevant, then when we calculate state \ ( i \) , we can use \ (f [i] \) to represent the state \ (i \) , then when the next time I need to use the state \ (i \) when I returned directly \ (f [i] \) can be.

No after-effect concept: the
status of a stage once determined, after which evolution is no longer influenced by previous decisions of the various states and, simply put, is "the future has nothing to do with the past", the current state is the previous history of a complete summary of the previous history only to influence the future evolution of the process by the current state. Specifically, if a problem is divided after each stage, the state of phase I only come through the state equation transferred from Phase I-1 in a state, there is no relationship with the other states, in particular no relation to the state does not occur . From the perspective of graph theory to consider, if the transfer is defined between the apex of this problem in the state definition and drawing, two states as an edge, the weight increment is defined during transfer to the edge weights, constituting a directed acyclic weighted graph, therefore, the map may be "topological sorting", at least in the order of their topological sort to partitioning phase.

We mainly on the most basic in this lecture in memory memories resolve linear DP law.
In fact, we can see that if a solution is added on a search of memory, then he would solve the "optimal substructure" and "overlapping sub-problems", it becomes a recursive version of the dynamic programming.

Description:
The following examples which we will explain the solution for circulation and memory, search the wording of these questions.
Although the wording in the loop for us this lesson to write them more convenient and well understood, but want to be sure the students understand and master the memory search wording, because our next course sections will search the memory has a very important relationship .

Example 1 rises longest sequence

Topic effect:
to give you a length \ (n \) is the number of columns \ (A_1, A_2, \ cdots, A_N \) , you find out its longest rising sequence length.
Rise up sequence: the sequence without exchange sequence \ (A \) selecting a number of elements (sub-sequences need not continuously) so that the front element necessarily smaller than the latter element. Corresponding increase in the longest sequence is the longest sequence rise.
We generally referred to as "the longest rising sequence" for the LIS (Longest Increasing Subsequence).

Problem-solving ideas:
the reset state \ (f [i] \) represented by (a_i \) \ end (and comprising \ (a_i \) ) increased the longest sequence length, then:

  • \ (f [i] \) is at least \ (1 \) ;
  • \ (F [I] = \ max (F [J]) \) +. 1, where \ (J \) satisfies \ (0 \ le j \ lt i \) and \ (a [j] \ lt a [i ] \) .

Code demonstrates

First we define the array and the necessary variables:

int n, a[1010], f[1010], ans;

among them:

  • \ (n-\) represents the number of array elements;
  • \ (A \) array for storing values, \ (A [I] \) represents the array \ (I \) values of elements;
  • \ (F \) array for storing state, \ (F [I] \) represented by \ (a [i] \) at the end of the LIS length;
  • \ (ans \) is used to store our final answer.

We then process the input:

cin >> n;
for (int i = 1; i <= n; i ++)
    cin >> a[i];

Then, we show you realize solved with a for loop \ (f [1] \) to \ (f [the n-] \) :

for (int i = 1; i <= n; i ++) {
    f[i] = 1;
    for (int j = 1; j < i; j ++) {
        if (a[j] < a[i]) {
            f[i] = max(f[i], f[j]+1);
        }
    }
}

Then our answer is \ (f [i] \) maximum:

for (int i = 1; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

Then, use the Search + memory of the way how to achieve it? as follows:

int dfs(int i) {
    if (f[i]) return f[i];
    f[i] = 1;
    for (int j = 1; j < i; j ++)
        if (a[j] < a[i])
            f[i] = max(f[i], dfs(j)+1);
    return f[i];
}

Memory search is also known as a memorandum , which memorandum we have here is our \ (f [i] \) .

  • If the dfs(i)first time it is called, \ (F [I] = 0 \) , performs a series of calculations;
  • However, if dfs(i)not the first time it is called, is bound \ (F [I] \ gt 0 \) , it dfs(i)directly returns \ (f [i] \) values, thus avoiding the problem of calculation sub reread.

So I was judge at the beginning of the function:
if \ (f [i] \) is not zero, then returned directly \ (f [i] \) .
Otherwise the calculation.

Then, we can calculate the answer in the following manner:

for (int i = 1; i <= n; i ++)
    ans = max(ans, dfs(i));
cout << ans << endl;

The general form of the complete code:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) {
        f[i] = 1;
        for (int j = 1; j < i; j ++) {
            if (a[j] < a[i])
                f[i] = max(f[i], f[j]+1);
        }
    }
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

Memory search form complete code:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int dfs(int i) {
    if (f[i]) return f[i];
    f[i] = 1;
    for (int j = 1; j < i; j ++)
        if (a[j] < a[i])
            f[i] = max(f[i], dfs(j)+1);
    return f[i];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, dfs(i));
    cout << ans << endl;
    return 0;
}

Examples 2 and maximum field

Subject to the effect:
we can "field" as "a continuous sequence."
The maximum field and the problem is solved and all contiguous subsequence among the largest and.

Outline of Solution:
First, we define the state \ (f [i] \) represented by (a [i] \) \ end (and comprising \ (A [I] \) ) and a maximum field.
Then we can get the state transition equation:
\ (F [I] = \ max (F [. 1-I], 0) + A [I] \)

First, we initialize the input section and the following (from coordinates \ (1 \) to the \ (n-\) ):

int n, a[1010], f[1010], ans;
cin >> n;
for (int i = 1; i <= n; i ++) 
    cin >> a[i];

Then in a manner solving the following general manner:

for (int i = 1; i <= n; i ++)
    f[i] = max(f[i-1], 0) + a[i];

Then our answer is all \ (f [i] \) maximum:

for (int i = 1; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

Recursive form, we also open a function dfs(i)for returning \ (f [i] \) values.
But here we can not pass \ (f [i] \) values determined \ (f [i] \) is already seeking out, so I'll open a bool type \ (vis \) array by (vis [i \ ] \) is determined \ (f [i] \) whether a request over.

bool vis[1010];

Memory search to achieve the following:

int dfs(int i) {
    if (i == 0) return 0;   // 边界条件
    if (vis[i]) return f[i];
    vis[i] = true;
    return f[i] = max(dfs(i-1), 0) + a[i];
}

Note: Search / recursive must pay attention to boundary conditions.

Then, the answer to solve Mode 1 is as follows:

ans = dfs(1);
for (int i = 2; i <= n; i ++)
    ans = max(ans, dfs(i));
cout << ans << endl;

Another way to solve the answer is as follows:

dfs(n);
ans = f[1];
for (int i = 2; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

There are not found, here I would call a \ (the DFS (the n-) \) , all of \ (f [i] (1 \ le i \ le n) \) value on all seek out yet.
Because my first seeking \ (dfs (n) \) when calls \ (dfs (n-1) \) , and for the first time \ (dfs (n-1) \) will be called \ (dfs (the n--2) \) , ......, for the first time \ (dfs (2) \) will be called \ (the DFS (1) \) .

So call it \ (the DFS (the n-) \) , I put all of \ (f [i] \) are seeking out.

The general form of the complete codes are as follows:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++)
        f[i] = max(f[i-1], 0) + a[i];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

Complete implementation of the code memory search are as follows:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
bool vis[1010];
int dfs(int i) {
    if (i == 0) return 0;   // 边界条件
    if (vis[i]) return f[i];
    vis[i] = true;
    return f[i] = max(dfs(i-1), 0) + a[i];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    // ans = dfs(1);
    // for (int i = 2; i <= n; i ++)
    //     ans = max(ans, dfs(i));
    dfs(n);
    ans = f[1];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

3 Number of examples about Tower

Subject to the effect:
There are number of towers as shown below, the underlying requirements come from the top, each step can only come if the adjacent nodes, the nodes through the figures and is the largest number?

Problem-solving ideas:

First we make some assumptions: a total of \ (n \) line, the top of the line is the first \ (1 \) line, the bottom line is that the first \ (n \) line, we use the \ ((i, J) \) to indicate section \ (I \) row \ (J \) lattice, with \ (a [i] [j ] \) represents \ ((i, j) \ ) the value stored in the lattice.

We can see that from \ ((i, j) \ ) down to the bottom of the walk and the maximum number of grid from the bottom up and walk to the \ ((i, j) \ ) the maximum number and is equal.
So we can put the question becomes: under most any request from a grid walk up to (1,1) and the maximum number.
It can be found, so look through the conversion of thinking, we put a "top-up" problem converted into a "bottom-up" problem.
(Please have a good experience of "top-down" and "bottom-up" these two concepts, as we will discuss this question next two concepts in another scenario)

We can find, in addition to the lowest level (the first \ (i \) layer) is directly went to the accident, all the upper \ ((i, j) \ ) than from the \ ((i + 1, j ) \) came up , that is, from the \ ((i + 1, j + 1) \) onto the past.

Therefore, we may assume \ (f [i] [j ] \) represents an arbitrary position from the ground up to walk \ ((i, j) \ ) the maximum number and location.

It can be deduced:

  • Those \ (i = n \) o'clock, \ (f [i] [j] = a [i] [j] \) ;
  • \(i \lt n\) 时, \(f[i][j] = \max(f[i+1][j], f[i+1][j+1]) + a[i][j]\)

In the process of the deduced from the recall \ (n-\) to \ (1 \) traverse \ (I \) , because high-level state of the first derived by low-level state.

Main code segment that implements the following general form:

for (int i = n; i >= 1; i --) { // 自底向上
    for (int j = 1; j <= n; j ++) {
        if (i == n)
            f[i][j] = a[i][j];
        else
            f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
    }
}

Can be found, we use a general form of a written, is transitioned to solve the lower layer, and then derive the low-level state by the high level of transient, so we can say that this idea is achieved from the bottom up to.

Finished the general form of implementation, let's explain the use of the memory of the search in the form of implementation were solved.

We also still have to define a state \ (f [i] [j ] \) represents the position come from any of a bottom \ ((i, j) \ ) the number and the maximum (and the same as described above).

But we are not in a general form above to solve \ (f [i] [J] \) , but open a function dfs(int i, int j)to solve \ (f [i] [J] \) .

So, how do we to the memory of : namely: to determine the current \ (f [i] [j ] \) have already visited it?

For a start, \ (f [i] [j ] \) are \ (0 \) , if the number of all elements in the column \ (a [i] [j ] \) were \ (\ gt 0 \) , then \ (f [i] [j ] \) upon request through the \ (f [i] [j ] \) must also \ (\ gt 0 \) .

However, if the \ (A [I] [J] \ GE 0 \) (i.e. \ (a [i] [j ] \) may be \ (0 \) ) or \ (a [i] [j ] \) can in the case of a negative number, we can not rely on \ (f [i] [j ] \) whether \ (0 \) to determine \ ((i, j) \ ) this grid has not visited the ( carefully think about Why ).

So the most reliable, the most difficult way is wrong with using the same manner with Example 2, open a two-dimensional \ (vis \) array, with \ (vis [i] [j ] \) to identify \ ((i , j) \) Have you visited.

Fragment main memory search form as follows:

int dfs(int i, int j) { // dfs(i,j)用于计算并返回f[i][j]的值
    if (vis[i][j]) return f[i][j];
    vis[i][j] = true;
    if (i == n) // 边界条件——最底层
        return f[i][j] = a[i][j];
    return f[i][j] = max(dfs(i+1, j), dfs(i+1, j+1)) + a[i][j];
}

The general form of complete code as follows:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
int n, a[maxn][maxn], f[maxn][maxn];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            cin >> a[i][j];
    for (int i = n; i >= 1; i --) { // 自底向上
        for (int j = 1; j <= n; j ++) {
            if (i == n) f[i][j] = a[i][j];
            else f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
        }
    }
    cout << f[1][1] << endl;
    return 0;
}

Memory search complete code is as follows:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
int n, a[maxn][maxn], f[maxn][maxn];
bool vis[maxn][maxn];
int dfs(int i, int j) { // dfs(i,j)用于计算并返回f[i][j]的值
    if (vis[i][j]) return f[i][j];
    vis[i][j] = true;
    if (i == n) // 边界条件——最底层
        return f[i][j] = a[i][j];
    return f[i][j] = max(dfs(i+1, j), dfs(i+1, j+1)) + a[i][j];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            cin >> a[i][j];
    cout << dfs(1, 1) << endl;
    return 0;
}

Guess you like

Origin www.cnblogs.com/quanjun/p/12157637.html