2020年4月最后一周

学习内容

这周进行区间DP的学习,区间DP的内容理解起来容易实现起来难,多重循环经常晕头转向,在实现时一定要时刻注意数据代表的意义,并对其进行深度剖析。

概述

区间dp,顾名思义,在区间上dp,大多数题目的状态都是由区间(类似于dp[l][r]这种形式)构成的,就是我们可以把大区间转化成小区间来处理,然后对小区间处理后再回溯的求出大区间的值,主要的方法有两种,记忆化搜索和递推。

在用递推来求解时,关键在于递推是for循环里面的顺序,以及dp的关键:状态转移方程。
 

//mst(dp,0) 初始化DP数组
for(int i=1;i<=n;i++)
{
    dp[i][i]=初始值
}
for(int len=2;len<=n;len++)  //区间长度
for(int i=1;i<=n;i++)        //枚举起点
{
    int j=i+len-1;           //区间终点
    if(j>n) break;           //越界结束
    for(int k=i;k<j;k++)     //枚举分割点,构造状态转移方程
    {
        //状态转移方程
    }
}

题目解决

石子合并

有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

输入

有多组测试数据,输入到结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开

输出

输出总代价的最小值,占单独的一行

样例输入

3

1 2 3

7

13 7 8 16 21 4 18

 样例输出

9

239

思路:

我们dp[i][j]来表示合并第i堆到第j堆石子的最小代价,那么每一堆都能拆分成两小堆,枚举不同的拆分方法,就能找到最小代价。

状态转移方程是

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);

其中w[i][j]表示把两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询,我们可以用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1].

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<cmath>
#include<iomanip>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;
int main()
{
    int stone[200],n,dp[200][200],sum[200];
    while(~scanf("%d",&n))
	{ 
	    memset(dp,0,sizeof(dp));
        scanf("%d",&stone[0]);
		sum[0]=stone[0];
        for(int i=1;i<n;++i)
		scanf("%d",&stone[i]),sum[i]=stone[i]+sum[i-1];
        for(int i=n-2;i>=0;i--)
		{
            for(int j=i+1;j<n;++j)
			{
			    dp[i][j]=INF;
			}
		}
        for(int i=n-2;i>=0;i--)
		{
            for(int j=i+1;j<n;++j)
			{
                for(int k=i+1;k<=j;++k)
				{
                    dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k][j]+sum[j]-sum[i-1]);
                }
            }
        }
    printf("%d\n",dp[0][n-1]);    
    }
    return 0;
}        

括号匹配

We give the following inductive definition of a “regular brackets” sequence:

the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]

Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.

Input

The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.

Output

For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

Sample Input

((()))
()()()
([]])
)[)(
([][][)
end
Sample Output

6
6
4
0
6
题意:最大括号匹配数

定义dp [ i ] [ j ] 为串中第 i 个到第 j 个括号的最大匹配数目

那么我们假如知道了 i 到 j 区间的最大匹配,那么i+1到 j+1区间的是不是就可以很简单的得到。

那么 假如第 i 个和第 j 个是一对匹配的括号那么 dp [ i ] [ j ] = dp [ i+1 ] [ j-1 ] + 2 ;

那么我们只需要从小到大枚举所有 i 和 j 中间的括号数目,然后满足匹配就用上面式子dp,然后每次更新dp [ i ] [ j ]为最大值即可。

更新最大值的方法是枚举 i 和 j 的中间值,然后让  dp[ i ] [ j ] = max ( dp [ i ] [ j ] , dp [ i ] [ f ] + dp [ f+1 ] [ j ] ) 。
 

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<cmath>
#include<iomanip>
#include<iostream>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define LL long long
#define MAXN 202
#define INF 999999
using namespace std;
int dp[MAXN][MAXN];
int main()
{
    string s;
    while(cin>>s)
    {
        if(s=="end")  break;
        memset(dp,0,sizeof(dp));
        int i,j,k,f,len=s.size();
        for(i=1; i<len; i++)
            for(j=0,k=i; k<len; j++,k++)
            {
                if((s[j]=='('&&s[k]==')')||(s[j]=='['&&s[k]==']'))
                    dp[j][k]=dp[j+1][k-1]+2;
                for(f=j; f<k; f++)
                    dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);
            }
        cout<<dp[0][len-1]<<endl;
    }
    return 0;
}

区数字

Description

The multiplication puzzle is played with a row of cards, each containing a single positive integer. During the move player takes one card out of the row and scores the number of points equal to the product of the number on the card taken and the numbers on the cards on the left and on the right of it. It is not allowed to take out the first and the last card in the row. After the final move, only two cards are left in the row.

The goal is to take cards in such order as to minimize the total number of scored points.

For example, if cards in the row contain numbers 10 1 50 20 5, player might take a card with 1, then 20 and 50, scoring
10*1*50 + 50*20*5 + 10*50*5 = 500+5000+2500 = 8000

If he would take the cards in the opposite order, i.e. 50, then 20, then 1, the score would be
1*50*20 + 1*20*5 + 10*1*5 = 1000+100+50 = 1150.
Input

The first line of the input contains the number of cards N (3 <= N <= 100). The second line contains N integers in the range from 1 to 100, separated by spaces.
Output

Output must contain a single integer - the minimal score.
Sample Input

6
10 1 50 50 20 5
Sample Output

3650
 

题意

一系列的数字,除了头尾不能动,每次取出一个数字,这个数字与左右相邻数字的乘积为其价值,最后将所有价值加起来,要求最小值

此题把我困住了好久,问题在一开始就难住了:如何设DP数组,我搜索并尝试了一下方法:

由k表示的数决定的子序列的宽度,依次递增宽度,并且移动子序列,即改变i的值,然后对i到i+k之间的元素进行遍历,比较dp[i][i+k]和先选取a[i]和a[j]之间的数,a[j]和a[i+k]之间的数,最后只剩下a[j],这一过程中的得到的和dp[i][j]+dp[j][i+k]+a[i]*a[i+k]*a[j]与原来的和的大小。

1.两侧开区间

如题,dp[i][j]的含义为“i右侧取到j左侧”,那么很显然可以将答案储存在dpij中,那么结果就为dp1n,代码如下

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<cmath>
#include<iomanip>
#include<iostream>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define LL long long
int dp[110][110];
int main()
{
    int i,j,k,len,a[110];
    int n;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(len=2;len<=n;len++)
        for(i=1;i+len<=n;i++)
        {
            dp[i][i+len]=INF;
            for(j=i+1;j<i+len;j++)
                dp[i][i+len]=min(dp[i][i+len],dp[i][j]+dp[j][i+len]+a[i]*a[i+len]*a[j]);
        }
    printf("%d\n",dp[1][n]);
    return 0;
}

2.左闭右开区间

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<cmath>
#include<iomanip>
#include<iostream>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define LL long long
int d[105][105],a[105];
using namespace std;
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
          cin>>a[i];
        memset(d,0,sizeof(d));
        for(int len=2;len<n;len++)
          for(int i=2;i+len-1<=n;i++)            
          {
              int j=i+len-1;
              d[i][j]=INF;
              for(int k=i;k<j;k++)
                d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]+a[i-1]*a[k]*a[j]);
          }
        cout<<d[2][n]<<endl;
    }
    return 0;
}

很显然第二种的最终答案储存在dp2n中,在循环边界上也与上一种有区别。这也可以更好的提醒我们在实现过程时一定要注意所设数组和变量的意义。

想法感悟

总的来说区间DP是一个好想但不太好实现的方法,这对使用熟练度有了更大的要求,本来就做的慢,在相同时间下做的就更少,只会越来越慢,也要提醒自己,在后面的学习中不要忘记回顾这方面的内容,经典题目好好保存着,时常回顾。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值