1. 程式人生 > >【專題】區間dp

【專題】區間dp

splay eve 回文 microsoft 傳送門 net sed 鏈接 bdc

1.【nyoj737】石子合並

傳送門:點擊打開鏈接

描述 有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]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]),dp[i][j]代表從i到j合並的最小值,sum[i][j]代表從i到j的價值和。

區間dp的循環是從小區間到大區間,根據這一規律寫出循環結構。

註意dp[i][i]是0。

代碼:

技術分享圖片
#include<bits/stdc++.h>  
using namespace std;  
int dp[205][205],a[205],sum[205][205];  
int main()  
{  
    int n,i,j,k;  
    while(cin>>n)  
    {  
        memset(sum,0,sizeof(sum));  
        memset(dp,0x3f3f3f3f,sizeof(dp));  
        
for(i=1; i<=n; i++) { scanf("%d",&a[i]); dp[i][i]=0; sum[i][i]=a[i]; } for(i=1; i<n; i++) { for(j=i+1; j<=n; j++) sum[i][j]+=sum[i][j-1]+a[j]; }
for(int len=2; len<=n; len++) for(i=1; i<=n-len+1; i++) { j=i+len-1; for(k=i; k+1<=j; k++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]); } printf("%d\n",dp[1][n]); } return 0; }
View Code

2.【nyoj37】回文字符串

傳送門:點擊打開鏈接

給你一個字符串,可在任意位置添加字符,最少再添加幾個字符,可以使這個字符串成為回文字符串。

樣例輸入

1
Ab3bd

樣例輸出

2

思路1:

利用反串找LCA

技術分享圖片
#include<bits/stdc++.h>  
using namespace std;  
char s[1005];  
int dp[1005][1005];  
int main()  
{  
    int t,i,j;  
    cin>>t;  
    while(t--)  
    {  
        scanf("%s",s);  
        int n=strlen(s);  
        memset(dp,0x3f3f3f3f,sizeof(dp));  
        dp[0][0]=0;  
        for(i=1; i<n; i++)  
            dp[i][i]=dp[i][i-1]=0;  
        for(int len=2; len<=n; len++)  
            for(i=0; i<=n-len; i++)  
            {  
                j=i+len-1;  
                if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1];  
                else dp[i][j]=min(dp[i+1][j]+1,dp[i][j-1]+1);  
            }  
        printf("%d\n",dp[0][n-1]);  
    }  
} 
View Code

思路2:

s[i]=s[j],dp[i][j]=dp[i+1][j-1];若!=,dp[i][j]=min(dp[i+1][j]+1,dp[i][j-1]+1)

解釋一下,假如回文串是「1123」,dp[1][4] = min(dp[1][3],dp[2][4]) + 1,先把「112」看成一個整體,dp[1][3] = 1,就是說再補一個就可以回文,最後加上一個「3」,也就是 +1 操作;「123」同理。

註意dp[i][i]=0,當len=2時,若s[i]=s[j]則dp[i][j]=0,所以初始化dp[i][i-1]也要全部賦為0

技術分享圖片
#include<bits/stdc++.h>  
using namespace std;  
char s[1005];  
int dp[1005][1005];  
int main()  
{  
    int t,i,j;  
    cin>>t;  
    while(t--)  
    {  
        scanf("%s",s);  
        int n=strlen(s);  
        memset(dp,0x3f3f3f3f,sizeof(dp));  
        dp[0][0]=0;  
        for(i=1; i<n; i++)  
            dp[i][i]=dp[i][i-1]=0;  
        for(int len=2; len<=n; len++)  
            for(i=0; i<=n-len; i++)  
            {  
                j=i+len-1;  
                if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1];  
                else dp[i][j]=min(dp[i+1][j]+1,dp[i][j-1]+1);  
            }  
        printf("%d\n",dp[0][n-1]);  
    }  
} 
View Code

3.【nyoj15】括號匹配(二)

傳送門:點擊打開鏈接

描述給你一個字符串,裏面只包含"(",")","[","]"四種符號,請問你需要至少添加多少個括號才能使這些括號匹配起來。
如:
[]是匹配的
([])[]是匹配的
((]是不匹配的
([)]是不匹配的

輸入
第一行輸入一個正整數N,表示測試數據組數(N<=10)
每組測試數據都只有一行,是一個字符串S,S中只包含以上所說的四種字符,S的長度不超過100
輸出
對於每組測試數據都輸出一個正整數,表示最少需要添加的括號的數量。每組測試輸出占一行
樣例輸入
4
[]
([])[]
((]
([)]
樣例輸出
0
0
3
2
思路:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]),特殊情況當s[i]與s[j]匹配時,dp[i][j]=min(dp[i][j],dp[i+1][j-1])
註意當截取長度為2且s[i]與s[j]匹配時,不需要添加符號,所以初始化dp[i][i-1]=0,dp[i][i]=1。
技術分享圖片
#include<bits/stdc++.h>  
using namespace std;  
char s[105];  
int dp[105][105];  
int main()  
{  
    int t,i,j,k;  
    cin>>t;  
    while(t--)  
    {  
        scanf("%s",s);  
        int n=strlen(s);  
        memset(dp,0x3f3f3f3f,sizeof(dp));  
        dp[0][0]=1;  
        for(i=1; i<n; i++)  
        {  
            dp[i][i]=1;  
            dp[i][i-1]=0;  
        }  
        for(int len=2; len<=n; len++)  
            for(i=0; i<=n-len; i++)  
            {  
                j=i+len-1;  
                if(s[i]==[&&s[j]==]||s[i]==(&&s[j]==))  
                    dp[i][j]=dp[i+1][j-1];  
                for(k=i; k+1<=j; k++)//這裏不是else  
                    dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][j]);  
            }  
        printf("%d\n",dp[0][n-1]);  
    }  
}
View Code

4.【nyoj746】整數劃分(四)

傳送門:點擊打開鏈接

給出兩個整數 n , m ,要求在 n 中加入m - 1 個乘號,將n分成m段,求出這m段的最大乘積

輸入
第一行是一個整數T,表示有T組測試數據
接下來T行,每行有兩個正整數 n,m ( 1<= n < 10^19, 0 < m <= n的位數);
輸出
輸出每組測試樣例結果為一個整數占一行
樣例輸入
2
111 2
1111 2
樣例輸出
11
121

思路:

由於最大的劃分所有的乘號都是固定的,可以從前到後以此找出所有乘號的位置,找出最優子結構(從結論入手向前拆分問題)。

dp[i][j]代表0到i個數字中添加j個乘號,要求dp[n][m]即求dp[k][m-1]*a[k+1][i],應用到所有dp[i][j]=max(dp[i][j],dp[k][j-1]*a[k+1][i]),a[i][j]是從i到j的組合數。
技術分享圖片
#include<bits/stdc++.h>  
using namespace std;  
typedef long long LL;  
LL dp[25][25],a[25][25];  
char s[25];  
int main()  
{  
    LL n,m,t,i,j,k;  
    cin>>t;  
    while(t--)  
    {  
        scanf("%s%lld",s,&m);  
        n=strlen(s);  
        memset(dp,0,sizeof(dp));  
        for(i=0; i<n; i++)  
            a[i][i]=s[i]-0;  
        for(i=0; i<n-1; i++)  
            for(j=i+1; j<n; j++)  
                a[i][j]=a[i][j-1]*10+(s[j]-0);  
        for(i=0; i<n; i++)  
            dp[i][0]=a[0][i];  
        for(j=1; j<=m-1; j++)  
            for(i=1; i<n; i++)  
                for(k=0; k+1<=i; k++)  
                    dp[i][j]=max(dp[i][j],dp[k][j-1]*a[k+1][i]);  
        printf("%lld\n",dp[n-1][m-1]);  
    }  
    return 0;  
}  
View Code

【專題】區間dp