Dynamic programming: interval DP (learning record)

It can be said that it is the first part of self-study. At the beginning, it was because I met similar problems on oj. There was also a problem in the freshman competition (I don't want to recall...) that used this part of knowledge. Let's have a look. (there are many things I want to write in this article. I hope I won't lose it)

Example: 1045 stone merging 1     1048 stone merging 2     1178 energy Necklace     U187635 wall painting (easy)   

Interval DP: interval dynamic programming is an extension of linear dynamic programming. When dividing problems in stages, it has a great relationship with the order of elements in the stage and which elements from the previous stage are combined. (from Interval DP - Oi wiki (OI wiki. ORG) )Generally speaking, it is to carry out dynamic programming on the interval, solve the optimal planning between a section of cells, and obtain the optimal solution on the whole interval by merging the optimal solutions between each cell.

At the beginning of learning, I was very confused. I read it several times to clarify the meaning and function of each quantity. This is conducive to mastering knowledge points and writing code by myself~

Chain interval DP

Explain with a board question: 1045 stone merging 1

  We solve the minimum value of stone sequence merging, divide it into several cells, and solve the optimal solution between cells respectively.

AC code (disassembled: P):

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
#include <set>
#include <iterator>

using namespace std;

typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define INF 0x3f3f3f3f
//Judge whether to open ll!!!
//Determine whether initialization is required!!!
const int mod=1e9+7;
const int N=500;
int a[N],dif[N];
ll f[N][N];
int n;

In the definition part, f two-dimensional array is DP array. The first dimension refers to the starting point and the second dimension is the end point, that is, f array represents the optimal solution of stone merging on the starting point - > end point of the interval. dif array is prefix and array, because we require interval optimal solution, we can't help finding the sum of stones in a certain interval. Prefix sum is undoubtedly the most convenient solution.

int main()
{
//	freopen("test.in","r",stdin);
    cin>>n;
    memset(dif,0,sizeof(dif));
    memset(f,INF,sizeof(f));
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	dif[i]=dif[i-1]+a[i];
    	f[i][i]=0;
	}
	for(int len=1;len<=n;len++)//Interval length 
	{
		for(int j=1;j+len-1<=n;j++)//Calculation starting point 
		{
			int ends=j+len-1;
			for(int i=j;i<ends;i++)//Split point
			{
				f[j][ends]=min(f[j][ends],f[j][i]+f[i+1][ends]+dif[ends]-dif[j-1]);
			}//The optimal solution between cells is continuously updated from the starting point of the sequence 
		}
	}
	cout<<f[1][n]<<'\n';
	return 0;
}

The main function part is explained in the notes. What needs to be specially explained is our state transition equation: the optimal solution between a certain cell = min (the original value, the optimal solution of the left section of the partition point I between cells + the optimal solution of the right section of I + the sum of continuous stone sequences between cells). In this way, we continuously update the inter cell optimal solution from the stone numbered 1. Then our final answer is the optimal solution from 1 to N, i.e. f[1][n], which can be output.

Then explain the range of each parameter: len inter cell length, range [1,n]; j the starting point of each cell can start with 1 and end with n+1-len (because each interval has a length, j can't get n. as for why to add 1, take a closer look by yourself); I the division point can be the starting point or the end point, or the starting point or the end point between cells. The range is from the starting point to the end point of an interval, i.e. [j,ends] (it may be strange to say this, but it is true...)

Initialization problem: the prefix and array can be initialized to 0. The f array is initialized according to whether it is the maximum value or the minimum value. If it is the minimum value in this problem, it is initialized to the maximum value.

Annular interval DP

Thinking: the above is to use interval DP in the sequence, but what if we want to merge a ring?

The main idea is to transform the ring into a chain and turn it into a problem we have solved. However, there are two ways to convert a ring into a chain: 1. Directly disconnect and enumerate each breakpoint. However, due to the enumeration n times, the time complexity is large, which is O(n^4); 2. If the chain is extended to 2n length, then the i-th term is the same as the n+i-th term. In this chain, dynamic programming is used to find the optimal solution, so the time complexity is O(n^3). In order to minimize the time complexity, we use the second discontinuous method here. (you can also try the first one, which will be very slow)

Explain in combination with the board questions: 1048 stone merging 2

  AC Code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
#include <set>
#include <iterator>

using namespace std;

typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define INF 0x3f3f3f3f
//Judge whether to open ll!!!
//Determine whether initialization is required!!!
const int mod=1e9+7;
const int N=500;
int a[N];
ll f[N][N];
ll dif[N];
ll ans=INF;
int n;

In the variable definition part, f two-dimensional array is DP array and a array is input array. Note that a array should be opened at least twice the data range size (otherwise WA will be generated), because we will convert the input array into twice according to the second discontinuous method, that is: 1 2 3 4 - > 1 2 3 4 1 2 3 4; dif is the prefix and array, and ans variable represents the last maximum result.

int main()
{
//	freopen("test.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	a[i+n]=a[i];//Construct double length single chain 
    	dif[i]=dif[i-1]+a[i];
	}
	for(int i=1;i<=n*2;i++)
	{
		dif[i]=dif[i-1]+a[i];//Construct prefixes and arrays 
		f[i][i]=0;//The length weight starting with i and ending with i is 0 
	}
	for(int len=1;len<=n;len++)//Interval length 
	{
		for(int i=1;i<=2*n-1;i++)//Starting point position 
		{
			int j=len+i-1;//End position 
			f[i][j]=INF;
			for(int k=i;k<=j;k++)//Discontinuous position 
			{
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+dif[j]-dif[i-1]);
			}
		} 
	}
	for(int i=1;i<=n;i++)
	{
		ans=min(ans,f[i][i+n-1]);
	}
	cout<<ans<<'\n';
	return 0;
}

The solution process is basically the same as the chain. The only thing to note is that when the answer ans is finally obtained, because it is a ring, the starting point is any one of 1~n, and the end point is the starting point + n-1. Therefore, it can be seen that the last for loop of the code traverses the optimal solution from 1~n, obtains it, assigns it to ans, and outputs it.

After understanding the general content of interval DP, let's take a look at the specific application of ring DP: 1178 energy Necklace

It should be noted that the difference in application lies in understanding the meaning of the question and modifying some details according to the meaning of the question, but the general direction of problem-solving remains the same.

Direct AC Code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
#include <set>
#include <iterator>

using namespace std;

typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define INF 0x3f3f3f3f
//Judge whether to open ll!!!
//Determine whether initialization is required!!!
const int mod=1e9+7;
const int N=300;
ll a[N],f[N][N];
ll ans;

int main()
{
//	freopen("test.in","r",stdin);
    int n;
    cin>>n;
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	a[i+n]=a[i];
	}
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len<=2*n+1;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++)
			{
				f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*a[j+1]);
			}
		}
	}
	ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,f[i][i+n-1]);
	}
	cout<<ans<<'\n';
	return 0;
}

The difference is that when updating the inter cell optimal solution, the position of the prefix sum is changed to the product of the energy release in the problem. Why use multiplication here? This requires us to read the questions carefully, and we also need to do more questions to exercise a similar way of thinking..

The following is an interval DP question of the sparse freshman competition I played. I didn't learn this part at that time. Now it's a late supplementary question for this question.

U187635 wall painting (easy)

Pay attention to reading questions. Many questions do have big problems because they can't understand them. Obviously, the cost of painting the wall here is a small obstacle to solving the problem. In the application of interval DP, we need to determine according to the meaning of the topic, and we need to update the meaning between cells.

  The following AC Codes:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
#include <set>
#include <iterator>

using namespace std;

typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define INF 0x3f3f3f3f
//Judge whether to open ll!!!
//Determine whether initialization is required!!!
const int mod=1e9+7;
const int N=2005;
int n,k;
int a[N],c[N][N];
int len;
ll f[N][30]; 

There are several points to pay attention to when defining variables: for the open f two-dimensional array, the first dimension represents the number of walls brushed (note that it starts from the first side), the second dimension represents the number of workers, and f[i][j] represents the minimum cost for j workers to brush [1,i] walls. The number of workers is a positive integer less than or equal to 20 according to the data range given in the topic, so it is sufficient to open the second dimension to 30 (minimize the occupied space); len is used to count and calculate the number of blocks connected to the same color wall.

int main()
{
//	freopen("test.in","r",stdin);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
    	for(int j=1;j<=k;j++)
    	{
    		f[i][j]=INF;//Initialize the dp array. If you want the minimum value, initialize it to the maximum value 
		}
	}
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		c[i][i]=1;//The cost of painting a wall is 1 
	}
	for(int i=1;i<=n;i++)//c array initialization, c[i][j] represents the cost of painting the I to j walls 
	{
		len=1; 
		for(int j=i+1;j<=n;j++)//Initialize the c array, that is, the salary required to brush the i-th to j-th walls 
		{
			if(a[j]==a[j-1]) 
			{
				c[i][j]=c[i][j-1]+len*2+1;//(1)
			    len++;
			}
			else 
			{
				c[i][j]=c[i][j-1]+1;
				len=1;
			}
		}
	}
	for(int i=1;i<=n;i++)
	f[i][1]=c[1][i];//Initializing the f array is the cost of painting the wall for one person 
	for(int i=1;i<=n;i++)//Enumeration brush to the i-th wall 
	{
		for(int j=2;j<=min(i,k);j++)//Number range: when i is less than the number k, the maximum is i, which can ensure that everyone has a wall brush; 
		{                           //When i is greater than k, the maximum value is K 
			for(int l=j-1;l<i;/*(2)*/l++)//Take the number of workers 2~j-1 as the interval, and update once for each additional person 
			{                     //After each update, it is the optimal solution of j people for brushing i walls 
				f[i][j]=min(f[i][j],f[l][j-1]+c[l+1][i]);
			}
		}
	} 
	cout<<f[n][k]<<'\n';
	return 0;
}

Main function part: the explanation of each step is in the note. There are two places that need to be explained separately: Note (1): the method used in this place is very ingenious. We calculate the cost of painting the wall. If there are continuous walls with the same color, how do we calculate the cost? It is known that the cost of continuous walls with the same color is calculated as follows: (I is the same number of continuous colors) i=1, cost=1; i=2,cost=4; i=3, cost=9... We can easily find the relationship between these numbers. It is convenient for us to update I while updating cost, 2,4,9,16... The difference between these numbers is an equal difference sequence! In this way, the part calculated with len is easy to understand. Note (2): why is l less than I? Firstly, l is the variable used to count the number of walls. In addition, what we want to update is the optimal solution of the first j-1 person + the last labor money. It is necessary to ensure that the last person can brush at least one wall, so "=" is not taken.

  :P 

os: how to say, I feel I can do this problem, and I may not be able to do it... (maybe it's because I'm too delicious)

Thinking: the problem now is that you don't know how to deal with some details of the application of the equation of state. You still need to practice more

If there is any mistake, please give advice

Orz

Tags: Algorithm Dynamic Programming

Posted on Thu, 25 Nov 2021 17:15:13 -0500 by mmosel