区间dp学习

· · 个人记录

区间dp

拆分型

将dp拆分为几个子区间,分别求解子问题,再通过合并子问题来解决大问题

P1775

顺便作为模版讲解,定义dp[l][r]为合并l到r的最小代价,为了得到区间,我们需要枚举l和长度来枚举r,然后设置分割点k将区间拆分为两个区间,最后算出代价的状态转移方程

dp[l][r]=min(dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1],dp[l][r]);

注意边界和起始值,要不影响答案,自己到自己距离为0,所以需要给dp数组极大值,然后dp[i][i]=0

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e2+7;
int a[maxn],sum[maxn];
int dp[maxn][maxn];
int main() {
    int n;
    cin>>n;
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
        dp[i][i]=0;
    }
    for(int len=1;len<=n;len++) {
        for(int l=1;l+len-1<=n;l++){
            int r=len+l-1;
            for(int k=l;k<r;k++) {
                dp[l][r]=min(dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1],dp[l][r]);
            }
        }
    }
    cout<<dp[1][n];
    return 0;
} 

P1622

和上一题类似,将牢房分割成q个区间,然后使用结构体存储每一个区间的l和r,然后向上一题一样设分割点,最后得出合并代价a[r].r-a[l].l

#include<bits/stdc++.h>
using namespace std;
int p,q;
const int maxn = 1e3+6;
struct Node{
    int l,r;
}a[maxn];
int dp[maxn][maxn];
int main() {
    cin>>p>>q;
    a[0].r=-1;
    for(int i=1;i<=q;i++) {
        int x;
        cin>>x;
        a[i].l=a[i-1].r+2;
        a[i].r=x-1;
    }
    a[q+1].l=a[q].r+2;
    a[q+1].r=p;
    int n=q+1;
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            dp[i][j]=INT_MAX;
            dp[i][i]=0;
        }
    }
    for(int len=2;len<=n;len++) {
        for(int l=1;l+len-1<=n;l++) {
            int r=l+len-1;
            for(int k=l;k<r;k++) {
                dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+a[r].r-a[l].l);
            }
        }
    }
    cout<<dp[1][q+1];
    return 0;
}

UVA348

要求我们给出格式
首先对于输出,我们可以把字符串看做左右儿子,用中序遍历完成字符串输出
注意枚举长度从2开始一直到n,然后套模版枚举l和len,注意状态转移方程为

if(dp[l][k]+dp[k+1][r]+q[l].n*q[k].m*q[r].m<dp[l][r]) {
                        dp[l][r]=dp[l][k]+dp[k+1][r]+q[l].n*q[k].m*q[r].m;
                        d[l][r]=k;
                    }
#include<bits/stdc++.h>
using namespace std;
const int maxn = 11;
struct Q {
    int n,m;
} q[maxn];
int dp[maxn][maxn];
int d[maxn][maxn];
void print(int l,int r)  {
    if(l==r) {
        cout<<"A"<<l;
        return;
    }
    int k=d[l][r];
    cout<<"(";
    print(l,k);
    cout<<" x ";
    print(k+1,r);
    cout<<")";
    return ;
}
int n;
int cnt;
int main() {
    while(cin>>n&&n) {
        cnt++;
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=n; j++) {
                dp[i][j]=1e9;
            }
        }
        for(int i=1; i<=n; i++) {
            dp[i][i]=0;
        }
        for(int i=1; i<=n; i++) {
            cin>>q[i].n>>q[i].m;
        }
        for(int len=2;len<=n;len++) {
            for(int l=1;l+len-1<=n;l++) {
                int r=l+len-1;
                for(int k=l;k<=r;k++) {
                    if(dp[l][k]+dp[k+1][r]+q[l].n*q[k].m*q[r].m<dp[l][r]) {
                        dp[l][r]=dp[l][k]+dp[k+1][r]+q[l].n*q[k].m*q[r].m;
                        d[l][r]=k;
                    }
                }
            }
        }
        cout<<"Case "<<cnt<<": ";
        print(1,n);
        cout<<"\n";
    }
    return 0;
}

两端扩展性

一般这种问题有个方向可以选择,于是我们这两种方案都要计算最后在整合

P2858

每个零食都可以卖以前的或者以后的,所以要把这两种情况都加上,然后公式化枚举起点和长度就行,注意状态转移方程内需要×持有天数

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3+7;
int a[maxn];
int dp[maxn][maxn];
int main() {
    int n;
    cin>>n;
    memset(dp,-0x3f,sizeof(dp));
    for(int i=1; i<=n; i++) {
        cin>>a[i];
        dp[i][i]=a[i]*n;
    }
    for(int len=2; len<=n; len++) {
        for(int l=1; l+len-1<=n; l++) {
            int r=len+l-1;
            dp[l][r]=max(dp[l][r],max(dp[l+1][r]+a[l]*(n-r+l),dp[l][r-1]+a[r]*(n-r+l)));
        }
    }
    cout<<dp[1][n];
    return 0;
}

P3205

对于每个点,我们选择这个点左边的人
对于这个人选择左边

dp[l][r][0]+=dp[l+1][r][0]%mod;

选择右边

dp[l][r][0]+=dp[l+1][r][1]%mod;

然后选这个点右边的人 对于这个人选择左边

dp[l][r][1]=(dp[l][r][1]%mod+dp[l][r-1][0]%mod)%mod;

选择右边

dp[l][r][1]=((dp[l][r][1]%mod+dp[l][r-1][1]%mod)+mod)%mod;
最后输出答案dp[1][n][0]+dp[1][n][1];```
#include<bits/stdc++.h>
using namespace std;
#define mod 19650827
const int maxn = 2e3+7;
int a[maxn];
int dp[maxn][maxn][2];
int main() {
    int n;
    cin>>n;
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=n; i++) {
        cin>>a[i];
        dp[i][i][0]=1,dp[i][i][1]=0;
    }
    for(int len=2; len<=n; len++) {
        for(int l=1; l+len-1<=n; l++) {
            int r=len+l-1;
            if(a[l]<a[l+1]) {
                dp[l][r][0]+=dp[l+1][r][0]%mod;
            }
            if(a[l]<a[r]) {
                dp[l][r][0]+=dp[l+1][r][1]%mod;
            } 
            if(a[l]<a[r]) {
                dp[l][r][1]=(dp[l][r][1]%mod+dp[l][r-1][0]%mod)%mod;
            }
            if(a[r-1]<a[r]) {

            }
            dp[l][r][1]%=mod;
            dp[l][r][0]%=mod;
        }
    }
    cout<<(dp[1][n][1]+dp[1][n][0])%mod;
    return 0;
}