8.14总结

区间 DP

拆分型区间 DP 是一种用于解决序列分割问题的动态规划方法。核心思想是将一个区间拆分成若干子区间,分别求解子问题,再通过合并子问题的解得到原问题的解。关键特征如下:

  1. 问题结构:问题涉及线性序列(如数组、字符串),需要将序列分割成连续子段进行处理。
  2. 状态定义:通常定义 dp[l][r] 表示区间 [l, r] 的最优解。
  3. 转移方式:枚举分割点 k,将区间拆分为 [l, k][k+1, r],通过子区间解合并得到当前解。
  4. 求解顺序:按区间长度从小到大递推,先求解小区间,再求解大区间。
  5. 初始化:最小子区间(如 l=r)直接初始化。

时间复杂度通常为 \(O (n^3)\)


T1: P1775 石子合并(弱化版)

题目简述

有 N 堆石子排成一列,每次合并相邻两堆,代价为两堆石子质量之和,求最小总合并代价。

详细思路

  1. 状态定义dp[l][r] 表示合并区间 [l, r] 的最小代价。
  2. 初始化
    • 单堆石子:dp[i][i] = 0(无需合并)。
  3. 前缀和:预处理 sum[i] 为前 i 堆石子质量和,快速计算区间和 sum[r] - sum[l-1]
  4. 状态转移
    • 枚举区间长度 len 从 2 到 N。
    • 枚举起点 l,计算终点 r = l + len - 1
    • 枚举分割点 kl ≤ k < r),将区间拆分为 [l, k][k+1, r]
    • 转移方程:
      dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r] + sum[r] - sum[l-1])
  5. 结果dp[1][N] 为全局最小代价。
  6. 时间复杂度\(O(n^3)\)

代码

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int maxn=1e3+5,mod=1e9+7,inf=1e18;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();return x*f;}
void write(int x){if(x<0){putchar('-'),x=-x;}if(x>9){write(x/10);}putchar(x%10+'0');return;}
int fpow(int a,int b,int p){if(b==0){return 1;}int res=fpow(a,b/2,p)%p;if(b%2==1){return((res*res)%p*a)%p;}else{return(res*res)%p;}}
int n,dp[maxn][maxn],a[maxn];
int sum[maxn];
signed main(){
    cin>>n;
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=n;i++){
        cin>>a[i];
        dp[i][i]=0;
        sum[i]=sum[i-1]+a[i];
    }
    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]+sum[r]-sum[l-1]);
            }
        }
    }
    cout<<dp[1][n];
    return 0;
}

T2: P1622 释放囚犯

题目简述

P 个牢房排成一列,释放 Q 个指定囚犯。每次释放时,与该囚犯所在连通块相邻的所有囚犯会发怒(需送肉)。求最小送肉人次。

详细思路

  1. 问题转化
    • 释放点将牢房序列分割为 (Q + 1) 个连续段(如释放点 [3,6,14] 分割为 [1-2], [4-5], [7-13], [15-20])。
    • 定义连续段结构体 Node{ l, r },存储每个段的左右端点。
  2. 状态定义dp[i][j] 表示处理第 i 到第 j 个连续段的最小代价。
  3. 初始化dp[i][i] = 0(单个连续段无需操作)。
  4. 状态转移
    • 枚举区间长度 len 从 2 到 (Q + 1)。
    • 枚举起点 lt,计算终点 rt = lt + len - 1
    • 枚举分割点 klt ≤ k < rt),合并子区间 [lt, k][k+1, rt]
    • 转移方程:
      dp[lt][rt] = min(dp[lt][rt], dp[lt][k] + dp[k+1][rt] + a[rt].r - a[lt].l)
      • 合并代价 a[rt].r - a[lt].l 表示当前大区间总囚犯数减 1(如 [1,20] 代价为 20-1=19)。
  5. 结果dp[1][Q+1] 为最小送肉人次。
  6. 时间复杂度\(O(Q^3)\)

代码

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int maxn=1e3+5,mod=1e9+7,inf=1e18;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();return x*f;}
void write(int x){if(x<0){putchar('-'),x=-x;}if(x>9){write(x/10);}putchar(x%10+'0');return;}
int fpow(int a,int b,int p){if(b==0){return 1;}int res=fpow(a,b/2,p)%p;if(b%2==1){return((res*res)%p*a)%p;}else{return(res*res)%p;}}
int p,dp[maxn][maxn];
struct Node{
    int l,r;
}a[maxn];
int q;
int sum[maxn];
signed 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]=inf;
        }
        dp[i][i]=0;
    }
    for(int len=2;len<=n;len++){
        for(int lt=1;lt+len-1<=n;lt++){
            int rt=lt+len-1;
            for(int k=lt;k<rt;k++){
                dp[lt][rt]=min(dp[lt][rt],dp[lt][k]+dp[k+1][rt]+a[rt].r-a[lt].l);
            }
        }
    }
    cout<<dp[1][q+1];
    return 0;
}

T3: UVA348 Optimal Array Multiplication Sequence

题目简述

给定 n 个矩阵的行列尺寸,通过括号化确定乘法顺序,使得标量乘法总次数最小。

详细思路

  1. 状态定义
    • dp[i][j] 表示计算矩阵链 [i, j] 的最小乘法次数。
    • d[i][j] 记录最优分割点 k(用于输出括号化方案)。
  2. 初始化dp[i][i] = 0(单个矩阵无需乘法)。
  3. 状态转移
    • 枚举区间长度 len 从 2 到 n。
    • 枚举起点 l,计算终点 r = l + len - 1
    • 枚举分割点 kl ≤ k < r),将链拆分为 [l, k][k+1, r]
    • 转移方程:
      cost = dp[l][k] + dp[k+1][r] + a[l].x * a[k].y * a[r].y
      cost < dp[l][r],则更新 dp[l][r] = cost 并记录 d[l][r] = k
  4. 输出方案:递归函数 print(l, r)
    • l == r,输出 "A" + l
    • 否则,在分割点 k = d[l][r] 处递归左右子区间,并添加括号。
  5. 时间复杂度\(O(n^3)\)

代码

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int maxn=17,mod=1e9+7,inf=1e18;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();return x*f;}
void write(int x){if(x<0){putchar('-'),x=-x;}if(x>9){write(x/10);}putchar(x%10+'0');return;}
int fpow(int a,int b,int p){if(b==0){return 1;}int res=fpow(a,b/2,p)%p;if(b%2==1){return((res*res)%p*a)%p;}else{return(res*res)%p;}}
int n,dp[maxn][maxn],d[maxn][maxn];
struct Node{
    int x,y;
}a[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<<")";
}
signed main(){
    int tmp=0;
    while(cin>>n){
        if(n==0) break;
        tmp++;
        memset(dp,0x3f3f3f3f,sizeof(dp));
        for(int i=1;i<=n;i++){
            cin>>a[i].x>>a[i].y;
            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++){
                    int cost=dp[l][k]+dp[k+1][r]+a[l].x*a[k].y*a[r].y;
                    if(cost<dp[l][r]){
                        dp[l][r]=cost;
                        d[l][r]=k;
                    }
                }
            }
        }
        cout<<"Case "<<tmp<<": ";
        print(1,n);
        cout<<endl;
    }
    return 0;
}
posted @ 2025-08-14 21:50  KK_SpongeBob  阅读(10)  评论(0)    收藏  举报