例题:以下例题部分的内容来自https://blog.csdn.net/my_sunshine26/article/details/77141398
一、石子合并问题
1.(NYOJ737)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737
分析:我们dp[i][j]来表示合并第i堆到第j堆石子的最小代价。那么状态转移方程为dp[i][j]=min(dp[i][k]+dp[k+1][j]+w[i][j]) (s[i][j-1]<=k<=s[i+1][j])
其中w[i][j]表示把两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询,我们可以用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1].
用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int maxn=210; 7 const ll inf=1e18; 8 ll dp[maxn][maxn]; 9 ll sum[maxn],a[maxn]; 10 int s[maxn][maxn]; 11 12 int main() 13 { 14 int n,i,j,k,x,y,z,len; 15 while ( scanf("%d",&n)!=EOF ) 16 { 17 for ( i=1;i<=n;i++ ) 18 { 19 for ( j=1;j<=n;j++ ) dp[i][j]=inf; 20 dp[i][i]=0; 21 s[i][i]=i; 22 } 23 sum[0]=0; 24 for ( i=1;i<=n;i++ ) 25 { 26 scanf("%lld",&a[i]); 27 sum[i]=a[i]+sum[i-1]; 28 } 29 for ( len=2;len<=n;len++ ) 30 { 31 for ( i=1;i<=n;i++ ) 32 { 33 j=len+i-1; 34 if ( j>n ) break; 35 for ( k=s[i][j-1];k<=s[i+1][j];k++ ) 36 { 37 if ( dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1] ) 38 { 39 dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; 40 s[i][j]=k; 41 } 42 } 43 } 44 } 45 printf("%lld\n",dp[1][n]); 46 } 47 return 0; 48 }
NYOJ737
2.(HDOJ3506)http://acm.hdu.edu.cn/showproblem.php?pid=3506
题意:上一题的升级版,将上一层的线性变成一个圈。这时候我们只需要将N变成n=2*N-1即可,最后ans=min(dp[i][i+n-1])
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int maxn=2010; 7 const ll inf=1e18; 8 ll dp[maxn][maxn]; 9 ll sum[maxn],a[maxn]; 10 int s[maxn][maxn]; 11 12 int main() 13 { 14 int n,i,j,k,x,y,z,len,N; 15 ll ans; 16 while ( scanf("%d",&N)!=EOF ) 17 { 18 n=2*N-1; 19 for ( i=1;i<=n;i++ ) 20 { 21 for ( j=1;j<=n;j++ ) dp[i][j]=inf; 22 dp[i][i]=0; 23 s[i][i]=i; 24 } 25 sum[0]=0; 26 for ( i=1;i<=N;i++ ) 27 { 28 scanf("%lld",&a[i]); 29 sum[i]=a[i]+sum[i-1]; 30 } 31 for ( i=1;i<N;i++ ) sum[i+N]=a[i]+sum[i+N-1]; 32 for ( len=2;len<=N;len++ ) 33 { 34 for ( i=1;i<=n;i++ ) 35 { 36 j=len+i-1; 37 if ( j>n ) break; 38 for ( k=s[i][j-1];k<=s[i+1][j];k++ ) 39 { 40 if ( dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1] ) 41 { 42 dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; 43 s[i][j]=k; 44 } 45 } 46 } 47 } 48 ans=inf; 49 for ( i=1;i<=N;i++ ) 50 { 51 j=i+N-1; 52 ans=min(ans,dp[i][j]); 53 } 54 printf("%lld\n",ans); 55 } 56 return 0; 57 }
HDOJ3506
二、括号匹配问题
1.(POJ2955)http://poj.org/problem?id=2955
题意:给出一个的只有‘(‘,‘)‘,‘[‘,‘]‘四种括号组成的字符串,求最多有多少个括号满足题目里所描述的完全匹配。
分析:用dp[i][j]表示区间[i,j]里最大完全匹配数。只要得到了dp[i][j],那么就可以得到dp[i-1][j+1] dp[i-1][j+1]=dp[i][j]+(s[i-1]于s[j+1]匹配?2:0).
然后利用状态转移方程更新一下区间最优解即可。dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j])
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int maxn=105; 7 char s[maxn]; 8 ll dp[maxn][maxn]; 9 10 int main() 11 { 12 int n,i,j,k,x,y,z,len; 13 while ( scanf("%s",s+1)!=EOF && s[1]!=‘e‘ ) 14 { 15 n=strlen(s+1); 16 memset(dp,0,sizeof(dp)); 17 for ( len=2;len<=n;len++ ) 18 { 19 for ( i=1;i<=n;i++ ) 20 { 21 j=i+len-1; 22 if ( j>n ) break; 23 if ( (s[i]==‘(‘&&s[j]==‘)‘) || (s[i]==‘[‘&&s[j]==‘]‘) ) dp[i][j]=dp[i+1][j-1]+2; 24 for ( k=i;k<j;k++ ) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]); 25 } 26 } 27 printf("%lld\n",dp[1][n]); 28 } 29 return 0; 30 }
POJ2955
2.(NYOJ15)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=15
分析:最少添加的括号数=总括号-最大匹配的括号数,代码于上一题基本一致
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int maxn=105; 7 char s[maxn]; 8 ll dp[maxn][maxn]; 9 10 int main() 11 { 12 int n,i,j,k,x,y,z,len,T; 13 scanf("%d",&T); 14 while ( T-- ) 15 { 16 scanf("%s",s+1); 17 n=strlen(s+1); 18 memset(dp,0,sizeof(dp)); 19 for ( len=2;len<=n;len++ ) 20 { 21 for ( i=1;i<=n;i++ ) 22 { 23 j=i+len-1; 24 if ( j>n ) break; 25 if ( (s[i]==‘(‘&&s[j]==‘)‘) || (s[i]==‘[‘&&s[j]==‘]‘) ) dp[i][j]=dp[i+1][j-1]+2; 26 for ( k=i;k<j;k++ ) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]); 27 } 28 } 29 printf("%lld\n",n-dp[1][n]); 30 } 31 return 0; 32 }
NYOJ15
三、整数划分问题
1.(NYOJ746)http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=746
分析:用dp[i][j]表示从第一位到第i位共插入j个乘号后乘积的最大值。根据区间DP的思想我们可以从插入较少乘号的结果算出插入较多乘号的结果。
方法是当我们要放第j的乘号时枚举放的位置。状态转移方程为dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i])。其中num[i][j]表示从s[i]到s[j]这段连续区间代表的数值。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 const int maxn=20; 7 ll dp[maxn][maxn]; 8 ll num[maxn][maxn]; 9 10 int main() 11 { 12 int T,n,m,i,j,k,x,y,z; 13 char s[maxn]; 14 scanf("%d",&T); 15 while ( T-- ) 16 { 17 scanf("%s%d",s+1,&m); 18 n=strlen(s+1); 19 memset(dp,0,sizeof(dp)); 20 for ( i=1;i<=n;i++ ) 21 { 22 num[i][i]=s[i]-‘0‘; 23 for ( j=i+1;j<=n;j++ ) num[i][j]=num[i][j-1]*10+s[j]-‘0‘; 24 } 25 for ( i=1;i<=n;i++ ) dp[i][0]=num[1][i]; 26 for ( j=1;j<m;j++ ) 27 { 28 for ( i=1;i<=n;i++ ) 29 { 30 for ( k=1;k<i;k++ ) dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i]); 31 } 32 } 33 printf("%lld\n",dp[n][m-1]); 34 } 35 return 0; 36 }
NYOJ746
原文地址:https://www.cnblogs.com/HDUjackyan/p/9123199.html