区间dp详解(入门到进阶)

目录

 

前言:

初级版:51Nod - 1021 石子合并(区间dp,时间复杂度)

思路:

状态转移方程:

AC代码:

中级版:HDU - 3506 Monkey Party(四边形不等式优化,时间复杂度)

思路:

四边形不等式:

状态转移方程:

AC代码:

高级版:HYSBZ - 3229 石子合并(GarsiaWachs算法优化,时间复杂度常数小)

思路:

AC代码:

究极版:POJ - 1738 An old Stone Game(GarsiaWachs算法优化+平衡树优化,时间复杂度)

思路:

AC代码:


前言:

区间dp主要用于解决是将小区间合并成大区间的一种算法,通过断点的转移来合并区间以达到状态转移。比如石子合并就是通过对前缀和的操作,通过左端点和控制区间长度来进行的。

题目链接请点击题目

初级版:51Nod - 1021 石子合并(区间dp,时间复杂度O(n^{3})

基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题

N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

 

例如: 1 2 3 4,有不少合并方法

1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)

1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)

1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)

 

括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。

Input

第1行:N(2 <= N <= 100)
第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)

Output

输出最小合并代价

Input示例

4
1
2
3
4

Output示例

19

思路:

通过前缀和记录区间和的情况,然后通过区间dp求解。

状态转移方程:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])(k\in [i,j])

AC代码:

#include
using namespace std;
const int N = 1e5 + 7;
const int inf = 0x3f3f3f3f;
int wight[N],dp[107][107],w;
int main()
{
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    memset(dp,inf,sizeof dp);
    memset(wight,0,sizeof wight);
    for(int i = 1;i <= n;++i){
        cin >> w;
        wight[i] = wight[i - 1] + w;
        dp[i][i] = 0;
    }
    for(int l = 1;l <= n;++l)
        for(int i = 1;i + l <= n;++i){
            int End = i + l;
            for(int j = i;j <= End;++j)
                dp[i][End] = min(dp[i][End],dp[j + 1][End] + dp[i][j] + wight[End] - wight[i - 1]);
        }
    cout << dp[1][n] << endl;
    return 0;
}

 

中级版:HDU - 3506 Monkey Party(四边形不等式优化,时间复杂度O(n^{2})

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 2623    Accepted Submission(s): 1053

Problem Description

Far away from our world, there is a banana forest. And many lovely monkeys live there. One day, SDH(Song Da Hou), who is the king of banana forest, decides to hold a big party to celebrate Crazy Bananas Day. But the little monkeys don't know each other, so as the king, SDH must do something. 
Now there are n monkeys sitting in a circle, and each monkey has a making friends time. Also, each monkey has two neighbor. SDH wants to introduce them to each other, and the rules are: 
1.every time, he can only introduce one monkey and one of this monkey's neighbor. 
2.if he introduce A and B, then every monkey A already knows will know every monkey B already knows, and the total time for this introducing is the sum of the making friends time of all the monkeys A and B already knows; 
3.each little monkey knows himself; 
In order to begin the party and eat bananas as soon as possible, SDH want to know the mininal time he needs on introducing. 

Input

There is several test cases. In each case, the first line is n(1 ≤ n ≤ 1000), which is the number of monkeys. The next line contains n positive integers(less than 1000), means the making friends time(in order, the first one and the last one are neighbors). The input is end of file.

Output

For each case, you should print a line giving the mininal time SDH needs on introducing.

Sample Input

8 5 2 4 7 6 1 3 9

Sample Output

105

思路:

该题是一个进阶版的石子合并问题,数据范围更大了,并且石子的排布变成了环状,显然1e3的数据范围,O(n^{3})的算法是相当无力的,于是我们采取四边形不等式来优化区间dp,以起到降维的作用。

四边形不等式:

ab+cd<=ac+bd(ac,bd为对角线)显然对于任意四个不为0的长度都是可以构成两个共一顶点两条边的四边形的如图:

区间dp详解(入门到进阶)_第1张图片

区间包含单调性通俗的讲就是被包含的小区间的w的值始终小于等于区间的w值,即w(p,j)<=w(i,q)

而四边形不等式指的是两个交错区间的w值之和始终小于等于大小两个区间的w值之和,即w(i,j)+w(p,q)<=w(p,j)+w(i,q)

若w函数满足上述性质,则我们dp[i][j]就满足上述性质,此时我们的最有决策点是具备单调性的,即设区间[i,j]的最有决策点是s(i,j),则s(i,j)<=s(i,j+1)<=s(i+1,j+1)

状态转移方程:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])(k\in [s(i,j-1),s(i+1,j)])

AC代码:

#include
using namespace std;
const int N = 2e3 + 7;
const int inf = 0x3f3f3f3f;
int dp[N][N],sum[N],path[N][N],t,n,ans;
void init()
{
    ans = inf;
    memset(sum,0,sizeof sum);
    memset(path,0,sizeof path);
    for(int i = 0;i <= (n << 1);++i)
        for(int j = 0;j <= (n << 1);++j)
            i == j ? dp[i][j] = 0,path[i][j] = i : dp[i][j] = inf;
}
int main()
{
    while(scanf("%d",&n) != EOF){
        init();
        for(int i = 1;i <= n;++i){
            scanf("%d",&t);
            sum[i] = sum[i - 1] + t;
        }
        for(int i = n + 1;i <= (n << 1);++i) sum[i] = sum[n] + sum[i - n];
        for(int len = 1;len <= n;++len){
            for(int i = 1;i + len <= (n << 1);++i){
                int End = len + i;
                for(int j = path[i][End - 1];j <= path[i + 1][End];++j){
                    if(dp[i][End] > dp[j + 1][End] + dp[i][j] + sum[End] - sum[i - 1]) path[i][End] = j;
                    dp[i][End] = min(dp[i][End],dp[j + 1][End] + dp[i][j] + sum[End] - sum[i - 1]);
                }
            }
        }
        for(int i = 1;i <= n;++i) ans = min(ans,dp[i][i + n - 1]);
        printf("%d\n",ans);
    }
    return 0;
}

高级版:HYSBZ - 3229 石子合并(GarsiaWachs算法优化,时间复杂度O(n^2)常数小)

Time Limit: 3 Sec  Memory Limit: 128 MB
Submit: 1151  Solved: 422
[Submit][Status][Discuss]

Description

  在一个操场上摆放着一排N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

  试设计一个算法,计算出将N堆石子合并成一堆的最小得分。

Input

  第一行是一个数N。

  以下N行每行一个数A,表示石子数目。

Output

  共一个数,即N堆石子合并成一堆的最小得分。

Sample Input

4
1 1 1 1

Sample Output

8

HINT

对于 100% 的数据,1≤N≤40000

对于 100% 的数据,1≤A≤200

思路:

将当前项和第n-2项进行比较,如果大于将两项合并并将数组左移,再逆序比较一次,如果满足合并数组右移。

AC代码:

#include
using namespace std;
const int N = 1e5 + 7;
int stone[N],n,ans,t,j;
void init()
{
    ans = 0;
    t = 1;
}
void combine(int k)
{
    int res = stone[k - 1] + stone[k];
    ans += res;
    for(int i = k;i < t - 1;++i) stone[i] = stone[i + 1];
    t--;j = 0;
    for(j = k - 1;j > 0 && stone[j - 1] < res;--j) stone[j] = stone[j - 1];
    stone[j] = res;
    while(j >= 2 && stone[j] >= stone[j - 2]){
        int d = t - j;
        combine(j - 1);
        j = t - d;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i = 0;i < n;++i) scanf("%d",&stone[i]);
    init();
    for(int i = 1;i < n;++i){
        stone[t++] = stone[i];
        while(t >= 3 && stone[t - 3] <= stone[t - 1]) combine(t - 2);
    }
    while(t > 1) combine(t - 1);
    printf("%d\n",ans);
    return 0;
}

究极版:POJ - 1738 An old Stone Game(GarsiaWachs算法优化+平衡树优化,时间复杂度O(nlogn)

Time Limit: 5000MS   Memory Limit: 30000K
Total Submissions: 4196   Accepted: 1231

Description

There is an old stone game.At the beginning of the game the player picks n(1<=n<=50000) piles of stones in a line. The goal is to merge the stones in one pile observing the following rules: 
At each step of the game,the player can merge two adjoining piles to a new pile.The score is the number of stones in the new pile. 
You are to write a program to determine the minimum of the total score. 

Input

The input contains several test cases. The first line of each test case contains an integer n, denoting the number of piles. The following n integers describe the number of stones in each pile at the beginning of the game. 
The last test case is followed by one zero. 

Output

For each test case output the answer on a single line.You may assume the answer will not exceed 1000000000.

Sample Input

1
100
3
3 4 3
4
1 1 1 1
0

Sample Output

0
17
8

思路:

这一题数据范围更大了,O(n^2)的复杂度显然是无法解决这个问题的,所以我们需要优化GarsiaWachs算法,这过程中我们使用到平衡树。

AC代码:

//#include
#include
using namespace std;
const int N = 1e5 + 7;
const int inf = 0x3f3f3f3f;
int stone[N],n,ans,t,j;
void init()
{
    ans = 0;
    t = 3;
    stone[0] = inf;
    stone[n + 1] = inf - 1;
}
void combine(int k)
{
    int res = stone[k - 1] + stone[k];
    ans += res;
    for(int i = k;i < t - 1;++i) stone[i] = stone[i + 1];
    t--;j = 0;
    for(j = k - 1;stone[j - 1] < res;--j) stone[j] = stone[j - 1];
    stone[j] = res;
    while(j >= 2 && stone[j] >= stone[j - 2]){
        int d = t - j;
        combine(j - 1);
        j = t - d;
    }
}
int main()
{
    while(scanf("%d",&n) != EOF && n){
        for(int i = 1;i <= n;++i) scanf("%d",&stone[i]);
        init();
        for(int i = 3;i <= n + 1;++i){
            stone[t++] = stone[i];
            while(stone[t - 3] <= stone[t - 1]) combine(t - 2);
        }
        while(t > 3) combine(t - 1);
        printf("%d\n",ans);
    }
    return 0;
}

 

你可能感兴趣的:(动态规划)