1. 程式人生 > >專題訓練之區間DP

專題訓練之區間DP

scanf 技術分享 ons con tails 描述 spa std pri

例題:以下例題部分的內容來自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

專題訓練之區間DP