ybtoj「动态规划」第2章 区间DP

A. 【例题1】石子合并

区间dp经典题

\(f_{l,r}\) 表示合并区间 \([l,r]\) 的最小花费

考虑如何转移:可以枚举断点 $k\ $ \(f_{l,r}=f_{l,k}+f_{k+1,r}+a[\text{l~r}]\)

可以维护前缀和 \(f_{l,r}=f_{l,k}+f_{k+1,r}+sum[r]-sum[l-1]\)

复杂度 \(O(n^3)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long 
const int N=1e3+5;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
int n,a[N],sum[N],ma[N][N],mi[N][N],ans1=0x3f3f3f3f,ans2;
signed main(){
    n=read();
    for(int i=1;i<=n;i++)a[i]=a[i+n]=read();
    for(int i=1;i<=(n<<1);i++)sum[i]=sum[i-1]+a[i];
    memset(mi,0x3f,sizeof(mi));
    for(int i=1;i<=(n<<1);i++)mi[i][i]=0;
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=(n<<1);l++){
            int r=l+len-1;
            for(int k=l+1;k<=r;k++){
                mi[l][r]=min(mi[l][r],mi[l][k-1]+mi[k][r]+sum[r]-sum[l-1]);
                ma[l][r]=max(ma[l][r],ma[l][k-1]+ma[k][r]+sum[r]-sum[l-1]);
            }
        }
    }
    for(int i=1;i<=n;i++){
        ans1=min(ans1,mi[i][i+n-1]);
        ans2=max(ans2,ma[i][i+n-1]);
    }
    printf("%d\n%d",ans1,ans2);
    return 0;
}

B. 【例题2】木板涂色

\(f_{l,r}\) 表示涂完 \([l,r]\) 的最小花费

初始状态:\(f_{i,i}=1\)

  • 首先考虑白嫖:如果 \(col[l]=col[l+1]\)\(col[r]=col[r-1]\) 涂上一次的时候多带一个 \(l\ 或\ r\)
    \(f_{l,r}=f_{l+1,r}\ 或\ f_{l,r}=f_{l,r-1}\)

  • 白嫖不了枚举断点:\(f_{l,r}=f_{l,k}+f_{k+1,r}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define ll long long 
const int N=1e3+5;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
int n,f[N][N];
char s[55];
signed main(){
    s[++n]=getchar();
    while(s[n]>='A'&&s[n]<='Z')s[++n]=getchar();n--;
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)f[i][i]=1;
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            if(s[l]==s[r]){
                f[l][r]=min(f[l][r-1],f[l+1][r]);
                continue;
            }
            for(int k=l+1;k<=r;k++){
                f[l][r]=min(f[l][r],f[l][k-1]+f[k][r]);
            }
        }
    }
    printf("%d\n",f[1][n]);
    return 0;
}

C. 【例题3】消除木块

由于每个颜色必然一次性全消 那么可以预处理出颜色段数量和长度

\(f_{l,r}\) 表示消完 \([l,r]\) 的最高得分 发现如果 \(r\) 后还有和它颜色一样的可能会影响答案

于是加进状态:\(f_{l,r,k}\) 表示消完 \([l,r]\) 后面连着 \(k\) 个和 \(r\) 颜色一样的块 的最高得分

  • 可以把右边直接消掉:\(f_{l,r,k}=f_{l,r-1,0}+f_{r,r,k}\)

  • 否则会尽量让几段同颜色连起来:\(f_{l,r,k}=f_{l,p,k+len[r]}+f_{p+1,r-1,0}\ (col[p]=col[r])\)

代码是记搜

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define inl inline 
#define ll long long 
#define ls k<<1
#define rs k<<1|1
#define mid (l+r>>1)
const int N=205;
const int M=1e3+5;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
int T,n,m,a[N],col[N],len[N],f[N][N][M];
int dfs(int l,int r,int k){
    if(l==r)return pow(len[r]+k,2);
    if(f[l][r][k]>=0)return f[l][r][k];
    int ans=dfs(l,r-1,0)+dfs(r,r,k);
    for(int i=l;i<=r-1;i++){
        if(col[i]^col[r])continue;
        ans=max(ans,dfs(l,i,k+len[r])+dfs(i+1,r-1,0));
    }
    return f[l][r][k]=ans;
}
signed main(){
    T=read();
    for(int t=1;t<=T;t++){
        n=read();m=0;
        for(int i=1;i<=n;i++){
            a[i]=read();
            if(a[i]^a[i-1]){
                col[++m]=a[i];
                len[m]=1;
            }else len[m]++;
        }
        memset(f,-0x3f,sizeof(f));
        printf("Case %d: %d\n",t,dfs(1,m,0));
    }
    return 0;
}

D. 【例题4】最大收益

\(f_{l,r}\) 表示消区间 \([l,r]\) 得到的最大价值

类似石子合并 维护 \(b\) 数组的前缀和

  • 最优情况:\([l+1,r-1]\) 被消完( \(f_{l+1,r-1}=b_{r-1}-b_l\) ) 且 \(a[l]+a[r]\le k\) 那么直接全消:\(f_{l,r}=b_r-b_{l-1}\)

  • 否则分段消:\(f_{l,r}=f_{l,k}+f_{k+1,r}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=805;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return x*f;
}
int n,k,a[N];
ll b[N],f[N][N];
signed main(){
    n=read();k=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[i]=b[i-1]+read();
    for(int len=2;len<=n;len++){
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            if(f[l+1][r-1]==b[r-1]-b[l]&&a[l]+a[r]<=k){
                f[l][r]=b[r]-b[l-1];
                continue;
            }
            for(int k=l;k<r;k++)
                f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]);
        }
    }
    printf("%lld\n",f[1][n]);
    return 0;
}

E. 【例题5】棋盘分割

方差的柿子推一下就会发现答案只与 \(\sum x_i\) 有关

二维区间dp 枚举横竖的切割线即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=20;
inl int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return x*f;
}
int n,a[N][N],f[N][N][N][N][N];
inl int sum(int lx,int ly,int rx,int ry){
    int ans=0;
    for(int i=lx;i<=rx;i++)
       for(int j=ly;j<=ry;j++)
         ans+=a[i][j];
    return ans*ans;
}
inl double x_(){
    double ans=0;
    for(int i=1;i<=8;i++){
       for(int j=1;j<=8;j++){
         ans+=a[i][j];
       }
    }
    return ans/n;
}
signed main(){
    n=read();
    for(int i=1;i<=8;i++)
       for(int j=1;j<=8;j++)
         a[i][j]=read();
    memset(f,0x3f,sizeof(f));
    for(int len1=1;len1<=8;len1++){
       for(int lx=1;lx+len1-1<=8;lx++){
         int rx=lx+len1-1;
         for(int len2=1;len2<=8;len2++){
          for(int ly=1;ly+len2-1<=8;ly++){
              int ry=ly+len2-1;
              f[lx][ly][rx][ry][0]=sum(lx,ly,rx,ry);
          }
         }
       }
    }
    for(int k=1;k<=n-1;k++){
       for(int len1=1;len1<=8;len1++){
         for(int lx=1;lx+len1-1<=8;lx++){
          int rx=lx+len1-1;
          for(int len2=1;len2<=8;len2++){
              for(int ly=1;ly+len2-1<=8;ly++){
                 int ry=ly+len2-1;
                 for(int kx=lx;kx<rx;kx++)
                   f[lx][ly][rx][ry][k]=min(f[lx][ly][rx][ry][k],min(f[lx][ly][kx][ry][k-1]+f[kx+1][ly][rx][ry][0],f[lx][ly][kx][ry][0]+f[kx+1][ly][rx][ry][k-1]));
                 for(int ky=ly;ky<ry;ky++)
                   f[lx][ly][rx][ry][k]=min(f[lx][ly][rx][ry][k],min(f[lx][ly][rx][ky][k-1]+f[lx][ky+1][rx][ry][0],f[lx][ly][rx][ky][0]+f[lx][ky+1][rx][ry][k-1]));
              }
          }
         }
       }
    }
    printf("%.3lf\n",sqrt((double)f[1][1][8][8][n-1]/n-pow(x_(),2)));
    return 0;
}
posted @ 2023-10-25 11:07  xiang_xiang  阅读(30)  评论(0)    收藏  举报