区间DP模型与题目详解acm

区间DP问题

  • #写在前面
    • ##石头合并
      • ----c++版
    • ##环形石头合并
      • ----c++版
    • ##能量项链
      • ----c++版
    • ##凸多边形的划分
      • ----c++版
    • ##加分二叉树
      • ----c++版
    • ##棋盘分割
      • ----c++版


#写在前面

区间DP模型与题目详解acm_第1张图片
一般有两种实现方式
区间DP模型与题目详解acm_第2张图片

##石头合并

https://www.acwing.com/problem/content/284/

区间DP模型与题目详解acm_第3张图片
区间DP模型与题目详解acm_第4张图片

----c++版

#include
#include
using namespace std;

const int N=310;

int n;
int s[N];
int f[N][N];

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    
    for(int i=1;i<=n;i++)s[i]+=s[i-1];
    
    for(int lenn=2;lenn<=n;lenn++)
        for(int i=1;i+lenn-1<=n;i++){
            int l=i,r=i+lenn-1;
            f[l][r]=1e9;
            for(int k=l;k<r;k++)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
            
        }
    printf("%d",f[1][n]);
    return 0;  
}

##环形石头合并

https://www.acwing.com/problem/content/1070/

具体思路:
如何转化成上一个问题来做?
上一题是给定一个区间,我们都可以求出区间内石子合并的最小代价
如何把环转化为区间?
区间DP模型与题目详解acm_第5张图片
环上每两个点连一条边,表示其可以合并,我们最多合并n-1次,所以n个点的环上最多会连n-1条边,
也就是最终会有一个缺口
将这个缺口展开,可以得到一条链
只需对这条链进行上题的石子合并即可
以缺口为分界点,枚举每个缺口,可以得到n条链
问题转化为对n条链分别做石子合并

上题的时间复杂度是n^3,
枚举缺口有n^4的复杂度,n=200时有8亿,会超时

这里有一种普遍的优化方式
我们本质上要求n条长度为n的链上的石子合并问题
我们把链复制一遍接到后边即可
区间DP模型与题目详解acm_第6张图片
再在所有长度是n的区间 f [ i, i+n-1 ] 中取最值即可
(2n)^3 ,n=200时有64000000,不会超时

----c++版

#include
#include
#include
using namespace std;

const int N=410, inf=0x3f3f3f3f;
int n;
int s[N],w[N];
int f[N][N], g[N][N];

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
    }
    
    for(int i=1;i<=n*2;i++)s[i]=s[i-1]+w[i];//预处理出前缀和
    
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);
    
    for(int len=1;len<=n;len++)
        for(int l=1; l+len-1<=n*2;l++){
            int r=l+len-1;
            if(len==1)f[l][r]=g[l][r]=0;//一堆就不用合并了
            else{
                for(int k=l;k<r;k++){
                    f[l][r]=min(f[l][r], f[l][k]+f[k+1][r]+s[r]-s[l-1]);
                    g[l][r]=max(g[l][r], g[l][k]+g[k+1][r]+s[r]-s[l-1]);
                }
            }
        }
        
    int minv=inf, maxv=-inf;
    for(int i=1;i<=n;i++){
        minv=min(minv, f[i][i+n-1]);
        maxv=max(maxv, g[i][i+n-1]);
    }
    cout<<minv<<endl<<maxv<<endl;
    return 0;
}

##能量项链

https://www.acwing.com/problem/content/322/
有点像矩阵乘法
区间DP模型与题目详解acm_第7张图片
链式的区间dp

集合还是以分界线的形式划分

取巧的表示珠子的方法
区间DP模型与题目详解acm_第8张图片
f[1,5] 就是答案

线性做法:
区间DP模型与题目详解acm_第9张图片
环形做法:
这题是把一个珠子断开,首位有相同的珠子
有n个珠子,有n种断开方式
和上一题差不多
区间DP模型与题目详解acm_第10张图片

在这里插入图片描述

----c++版

#include
#include
#include
using namespace std;

const int N=210;
int n;
int w[N];
int f[N][N];

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
    }
    
    for(int len=3; len<=n+1; len++)//长度是2表示只有一个矩阵,是不需要释放能量的 
        for(int l=1;l+len-1<=n*2;l++){
            int r=l+len-1;
            for(int k=l+1;k<r;k++)
                f[l][r]=max(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
        }
    
    int res=0;
    for(int i=1;i<=n;i++)res=max(res, f[i][i+n]);
    
    cout<<res<<endl;
    return 0;
}

##凸多边形的划分

https://www.acwing.com/problem/content/1071/
这题和上一题的状态转移方程完全一样

区间DP模型与题目详解acm_第11张图片
区间DP模型与题目详解acm_第12张图片
区间DP模型与题目详解acm_第13张图片

可以发现三角形的左右两边是独立的,也就是这个三角形将多边形划分成了3个部分,每个部分取最值

这题要写个高精度
一般先写不是高精度的过掉样例,再写成高精度的

----c++版

无高精度

#include
#include
#include
using namespace std;

const int N=55, M=35;//9*3+2+保险
const int inf=1e9;
int n;
int w[N];
int f[N][N];

int main(){
    //虽然是环,但实际上是一个链式dp
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    
    for(int len=3;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            f[l][r]=inf;
            for(int k=l+1;k<r;k++)
                f[l][r]=min(f[l][r], f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
        }
    cout<<f[1][n]<<endl;
    return 0;
}

偷懒数组模拟高精度

#include
#include
#include
using namespace std;

typedef long long ll;
const int N=55, M=35;//9*3+2+保险
const int inf=1e9;
int n;
ll w[N];
ll f[N][N][M];

void add(ll a[], ll b[]){
    static ll c[M];
    memset(c, 0, sizeof c);
    for(int i=0, t=0; i<M; i++){
        t+=a[i]+b[i];
        c[i]=t%10;
        t/=10;
    }
    memcpy(a, c, sizeof c);
}

void mul(ll a[], ll b){
    static ll c[M];
    memset(c, 0, sizeof c);
    ll t=0;
    for(int i=0;i<M;i++){
        t+=a[i]*b;
        c[i]=t%10;
        t/=10;
    }
    memcpy(a, c, sizeof c);
}

int cmp(ll a[], ll b[]){
    for(int i=M-1; i>=0; i--)//从最高位往前比
        if(a[i]>b[i])return 1;
        else if (a[i]<b[i])return -1;
    return 0;
}

void print(ll a[]){
    int k=M-1;
    while(k&&!a[k])k--;//去0
    while(k>=0)cout<<a[k--];
    cout<<endl;
}

int main(){
    //虽然是环,但实际上是一个链式dp
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    
    ll temp[M];
    for(int len=3;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            f[l][r][M-1]=1;//正无穷
            for(int k=l+1;k<r;k++){
                memset(temp, 0, sizeof temp);
                temp[0]=w[l];
                mul(temp, w[r]);
                mul(temp, w[k]);
                add(temp, f[l][k]);
                add(temp, f[k][r]);
                if(cmp(f[l][r], temp)>0)
                    memcpy(f[l][r], temp, sizeof temp);
            }
        }
    print(f[1][n]);
    return 0;
}

##加分二叉树

区间dp问题记录方案
https://www.acwing.com/problem/content/481/

区间DP模型与题目详解acm_第14张图片
前序遍历
区间DP模型与题目详解acm_第15张图片
中序遍历
区间DP模型与题目详解acm_第16张图片
后序遍历

区间DP模型与题目详解acm_第17张图片

----c++版

#include
#include
using namespace std;
#include
const int N=30;
int n;
int w[N];
int f[N][N], g[N][N];//g存某段区间上的跟节点

void dfs(int l, int r){//前序遍历
    if(l>r)return;
    int root=g[l][r];
    cout<<root<<' ';
    dfs(l, root-1);
    dfs(root+1, r);
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    
    for(int len=1;len<=n;len++)
        for(int l=1;l+len-1<=n;l++){
            int r=l+len-1;
            if(len == 1) {
                f[l][r]=w[l];//如果是叶节点,就等于本身分数
                g[l][r]=l;//跟节点就是他自己
            }
            else {
                for(int k=l ; k<=r ; k++){
                    int left = k==l ? 1:f[l][k-1];//如果左子树为空,就是1
                    int right = k==r ? 1:f[k+1][r];//如果右子树为空,就是1
                    int score = left*right +w[k];
                    if(f[l][r]<score){
                        f[l][r]=score;
                        g[l][r]=k;
                    }
                }
            }
        }
    cout<<f[1][n]<<endl;
    dfs(1, n);
    return 0;
}

##棋盘分割

https://www.acwing.com/problem/content/323/

区间DP模型与题目详解acm_第18张图片
区间DP模型与题目详解acm_第19张图片

----c++版

#include
#include
using namespace std;
//二维前缀和 + 记忆化搜索(状态表示太长了,有5维)
#include
#include
const int N=15, M=9;
const double inf=1e9;

int n, m=8;
int s[M][M];
double f[M][M][M][M][N];
double X;//平均数

double get(int x1, int y1, int x2, int y2){
    double sum=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]-X;
    return sum*sum/n;
}

double dp(int x1, int y1, int x2, int y2, int k){
    double &v = f[x1][y1][x2][y2][k];
    if(v>=0) return v;
    if(k==1) return v=get(x1, y1, x2, y2);//不能再切撩
    
    v=inf;
    for(int i=x1; i<x2; i++){//横着切
        v=min(v, dp(x1, y1, i, y2, k-1)+get(i+1, y1, x2, y2));//选上部分继续切
        v=min(v, dp(i+1, y1, x2, y2, k-1)+get(x1, y1, i, y2));//选下部分继续切
    }
    for(int i=y1; i<y2;i++){//竖着切
        v=min(v, dp(x1, y1, x2, i, k-1)+get(x1, i+1, x2, y2));//选左边
        v=min(v, dp(x1, i+1, x2, y2, k-1)+get(x1, y1, x2, i));//选右边继续切
    }
    return v;
}

int main(){
    cin>>n;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++){
            cin>>s[i][j];
            s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
        }
    memset(f, -1, sizeof f); //double数组存储是科学计数法,如果二进制全是1的表示是负无穷-NAN
    
    X=(double)s[m][m]/n;//注意这里是整数除法
    
    printf("%.3lf\n", sqrt(dp(1,1,8,8,n)));

    return 0;
}

你可能感兴趣的:(算法模板,acm,c++,区间dp,acwing,树的遍历)