动态规划23题解析

最近两周做了动态规划的23道经典好题,涉及到区间、树形、数位等三种动态规划类型,现在将这23道题的题解写在下面,方便大家借鉴以及我加深记忆。

upd at:20190814 20:46.T7二叉苹果树

1、石子合并

经典的区间DP问题,枚举合并的堆数作为阶段,设f[i][j]表示i->j这段区间内的最优方案,考虑在这段区间内枚举断点k,不难得到f[i][j]=min(f[i][k]+f[k+1][j]+sum(i,j))(最大值同理)。破环为链后直接进行DP即可。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
int n,a[1005],f[505][505],g[505][505],prefix[505],minn=21374404,maxn;
int main()
{
    //freopen("A.in","r",stdin);
    //freopen("A.out","w",stdout);
    memset(f,20,sizeof(f));
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        a[i+n]=a[i];
    }
    for(int i=1;i<=n*2;i++)
    {
        prefix[i]=prefix[i-1]+a[i];
        f[i][i]=0;
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=2*n-i+1;j++)
        {
            int end=i+j-1;
            for(int k=j;k<end;k++)
            {
                f[j][end]=min(f[j][end],f[j][k]+f[k+1][end]+prefix[end]-prefix[j-1]);
                g[j][end]=max(g[j][end],g[j][k]+g[k+1][end]+prefix[end]-prefix[j-1]);
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        minn=min(minn,f[i][i+n-1]);
        maxn=max(maxn,g[i][i+n-1]);
    }
    printf("%d\n%d\n",minn,maxn);
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

石子合并

2、能量项链

同“石子合并”,将第一题的求和换成题目指定的模拟规则即可。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
int n,a[1005],f[505][505],b[1005],maxn;
int main()
{
    //freopen("B.in","r",stdin);
    //freopen("B.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        a[i+n]=a[i];
    }
    for(int i=1;i<=n*2-1;i++)
    {
        b[i]=a[i+1];
    }
    b[n*2]=a[1];
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=2*n-i+1;j++)
        {
            int end=i+j-1;
            for(int k=j;k<end;k++)
            {
                f[j][end]=max(f[j][end],f[j][k]+f[k+1][end]+a[j]*b[k]*b[end]);
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        maxn=max(maxn,f[i][i+n-1]);
    }
    printf("%d\n",maxn);
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

能量项链

3、凸多边形的划分

设f[i][j]为i号节点到j号节点组成的凸多边形的最优剖分,我们可以在这段区间内找到一个非i、j的顶点k,剖分出一个以i,j,k为顶点的三角形和两个凸多边形.

然后问题就转化成了如何求这两个小凸多边形的和,从小区间到大区间转移的过程中,小区间是已经被计算过的,直接调用即可。

状态转移方程如下:f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[j]*a[k]);

注意,此题需要用高精度或int128

#include<iostream>
#include<cstdio>
#include<cstring>
#define int __uint128_t
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
void myitoa(int a,char* s)
{
    int w=0;
    while(a>0)
    {
        s[++w]=a%10+‘0‘;
        a/=10;
    }
    s[0]=w;
}
int n,a[505],f[110][110];
char s[110];
signed main()
{
    //freopen("C.in","r",stdin);
    //freopen("C.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=2;i<=n-1;i++)
    {
        for(int j=1;j<=n-i;j++)
        {
            int end=i+j;
            f[j][end]=1e30;
            for(int k=j+1;k<end;k++)
            {
                f[j][end]=min(f[j][end],f[j][k]+f[k][end]+a[j]*a[k]*a[end]);
            }
        }
    }
    myitoa(f[1][n],s);
    for(int i=s[0];i>=1;i--)
    {
        cout<<s[i];
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

凸多边形的划分

4、括号匹配

通过题目很容易发现这道题的边界值:当i和i+1可以匹配的时候,它们合并的代价为0,否则为2。直接按照区间DP模板进行合并,两部分的代价加起来取最小就是最优值,特别地,当两个端点本身就可以匹配的时候,还要和中间的再取一次min。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
char a[155];
int n,f[155][155];
int main()
{
    memset(f,0x3f,sizeof(f));
    cin>>(a+1);
    n=strlen(a+1);
    for(int i=1;i<=n;i++)f[i][i]=1;
    for(int i=1;i<=n;i++)
    {
        if(a[i]==‘(‘&&a[i+1]==‘)‘)f[i][i+1]=0;
        else if(a[i]==‘[‘&&a[i+1]==‘]‘)f[i][i+1]=0;
        else f[i][i+1]=2;
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=n-i+1;j++)
        {
            int end=i+j-1;
            for(int k=j;k<end;k++)
            {
                f[j][end]=min(f[j][end],f[j][k]+f[k+1][end]);
            }
            if(a[j]==‘(‘&&a[end]==‘)‘)f[j][end]=min(f[j][end],f[j+1][end-1]);
            else if(a[j]==‘[‘&&a[end]==‘]‘)f[j][end]=min(f[j][end],f[j+1][end-1]);
        }
    }
    cout<<f[1][n]<<endl;
    return 0;
}

括号匹配

5、分离与合体

需要记录合并的地方,然后递归输出即可。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
int n,a[505],f[505][505],b[505][505];
void dfs(int x,int y,int dep,int k)
{
    if(x>=y)return;
    if(dep==k)
    {
        printf("%d ",b[x][y]);
        return;
    }
    dfs(x,b[x][y],dep+1,k);
    dfs(b[x][y]+1,y,dep+1,k);
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=n-i+1;j++)
        {
            int end=i+j-1;
            for(int k=j;k<end;k++)
            {
                if(f[j][k]+f[k+1][end]+(a[j]+a[end])*a[k]>f[j][end])
                {
                    f[j][end]=f[j][k]+f[k+1][end]+(a[j]+a[end])*a[k];
                    b[j][end]=k;
                }
            }
        }
    }
    printf("%d\n",f[1][n]);
    for(int i=1;i<n;i++)
    {
        dfs(1,n,1,i);
    }
}

分离与合体

6、矩阵取数游戏

DP五分钟,高精两小时!!!!!!这题DP其实还蛮好想的,因为小区间是由大区间转移而来,所以每一个区间要么是由左边取一个数后得到,要么是由右边取一

数得到,不难得到如下的转移方程:

f[i][j]=max(f[i-1][j]+base[m-j+i-1]*a[i-1],f[i][j+1]+base[m-j+i-1]*a[j+1]);

最后由于空区间无法表示,最后还要再取一边max。

#include<iostream>
#include<cstring>
#include<cstdio>
#define int long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=0;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    if(f)return x;return -x;
}
int n,m,a[110],f[110][110][110],base[110][110],ans[110],maxn,s1[110],s2[110],s3[110],s4[110],s5[110],s6[110],s7[110],s8[110];
void Mark(int c[])
{
    for(int i=1;i<=30;i++)
    {
        c[i+1]+=c[i]/10000;
        c[i]%=10000;
    }
    for(int i=30;i>=1;i--)
    {
        if(c[i])
        {
            c[0]=i;break;
        }
    }
}
void Mul(int a[],int b,int c[])
{
    for(int i=1;i<=a[0];i++)c[i]=a[i]*b;
    Mark(c);
}
void Add(int a[],int b[],int c[])
{
    Mark(a);Mark(b);
    if(a[0]>b[0])c[0]=a[0];
    else c[0]=b[0];
    for(int i=1;i<=c[0];i++)c[i]=a[i]+b[i];
    Mark(c);
}
bool compare(int a[],int b[])
{
    Mark(a);Mark(b);
    if(a[0]<b[0])return 0;if(a[0]>b[0])return 1;
    for(int i=a[0];i>=1;i--)
    {
        if(a[i]<b[i])return 0;
        if(b[i]<a[i])return 1;
    }
    return 0;
}
void pre()
{
    base[0][0]=base[0][1]=1;
    for(int i=1;i<=m;i++)Mul(base[i-1],2,base[i]);
}
void write(int ans[])
{
    cout<<ans[ans[0]];
    for(int i=ans[0]-1;i>=1;i--)
    {
        cout<<ans[i]/1000;
        cout<<ans[i]/100%10;
        cout<<ans[i]/10%10;
        cout<<ans[i]%10;
    }
}
signed main()
{
    //freopen("F.in","r",stdin);
    //freopen("F.out","w",stdout);
    n=read();m=read();
    pre();
    while(n--)
    {
        memset(f,0,sizeof(f));
        memset(s5,0,sizeof(s5));
        memset(s6,0,sizeof(s6));
        for(int i=1;i<=m;i++)a[i]=read();
        for(int i=1;i<=m;i++)
        {
            for(int j=m;j>=i;j--)
            {
                memset(s1,0,sizeof(s1));
                memset(s2,0,sizeof(s2));
                memset(s7,0,sizeof(s7));
                memset(s8,0,sizeof(s8));
                Mul(base[m-j+i-1],a[i-1],s1);
                Add(s1,f[i-1][j],s7);
                Mul(base[m-j+i-1],a[j+1],s2);
                Add(s2,f[i][j+1],s8);
                if(compare(s7,s8))memcpy(f[i][j],s7,sizeof(f[i][j]));
                else memcpy(f[i][j],s8,sizeof(f[i][j]));
            }
        }
        for(int i=1;i<=m;i++)
        {
            memset(s3,0,sizeof(s3));
            memset(s4,0,sizeof(s4));
            Mul(base[m],a[i],s3);
            Add(f[i][i],s3,s4);
            if(compare(s4,s5))memcpy(s5,s4,sizeof(s5));
        }
        Add(ans,s5,s6);
        memcpy(ans,s6,sizeof(ans));
    }
    write(ans);
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}
/*
2 10
96 56 54 46 86 12 23 88 80 43
16 95 18 29 30 53 88 83 64 67
*/

矩阵取数游戏

7、二叉苹果树

建树后依次枚举给左右子树保留的树枝,并不难的记忆化搜索。

#include<iostream>
#include<cstring>
using namespace std;
int mp[1005][1005],n,q,l[1005],r[1005],a[1005],f[1005][1005];
void Maketree(int node)
{
    for(int i=1;i<=n;i++)
    {
        if(mp[node][i]==-1)continue;
        l[node]=i;a[i]=mp[node][i];
        mp[node][i]=mp[i][node]=-1;
        Maketree(i);
        break;
    }
    for(int i=1;i<=n;i++)
    {
        if(mp[node][i]==-1)continue;
        r[node]=i;a[i]=mp[node][i];
        mp[node][i]=mp[i][node]=-1;
        Maketree(i);
        break;
    }
}
int dfs(int u,int w)
{
    if(w==0)return 0;
    if(l[u]==0&&r[u]==0)return a[u];
    if(f[u][w])return f[u][w];
    for(int i=0;i<=w-1;i++)
    {
        f[u][w]=max(f[u][w],dfs(l[u],i)+dfs(r[u],w-i-1)+a[u]);
    }
    return f[u][w];
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    memset(mp,-1,sizeof(mp));
    cin>>n>>q;
    for(int x,y,z,i=1;i<n;i++)
    {
        cin>>x>>y>>z;
        mp[x][y]=mp[y][x]=z;
    }
    Maketree(1);
    cout<<dfs(1,q+1)<<endl;
    return 0;
}

二叉苹果树

原文地址:https://www.cnblogs.com/szmssf/p/11354589.html

时间: 2024-12-15 11:02:45

动态规划23题解析的相关文章

44个 Javascript 变态题解析 (上\下)

第1题 ["1", "2", "3"].map(parseInt) 知识点: Array/map Number/parseInt JavaScript parseInt 首先, map接受两个参数, 一个回调函数 callback, 一个回调函数的this值 其中回调函数接受三个参数 currentValue, index, arrary; 而题目中, map只传入了回调函数--parseInt. 其次, parseInt 只接受两个两个参数 s

44个 Javascript 变态题解析

原题来自: http://javascript-puzzlers.herokuapp.com/ 读者可以先去做一下感受感受. 当初笔者的成绩是 21/44... 当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了.... 不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧! 第1题 ["1", "2", "3"].map(parseInt) 知识点: Array/map Number/parseInt

Javascript 变态题解析

读者可以先去做一下感受感受. 当初笔者的成绩是 21/44... 当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了.... 不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧! 第1题 ["1", "2", "3"].map(parseInt) 知识点: Array/map Number/parseInt JavaScript parseInt 首先, map接受两个参数, 一个回调函数 callba

乾颐堂安德华为数通HCNA真题解析版(第2部分)

HCNA真题解析视频即将上线,敬请关注本博客以及乾颐堂官网书接上文:16 Interface GigabitEthernet0/0/1 Port link‐type trunk Port trunk allow‐pass vlan 2 to 4094 根据如上所示的命令输出,下列描述中正确的是()(多选)A GigabitEthernet0/0/1 不允许 VLAN1 通过B GigabitEthernet0/0/1 允许 VLAN1 通过C 如果要把 GigabitEthernet0/0/1

44个javascript 变态题解析

原题来自: javascript-puzzlers 读者可以先去做一下感受感受. 当初笔者的成绩是 21/44… 当初笔者做这套题的时候不仅怀疑智商, 连人生都开始怀疑了…. 不过, 对于基础知识的理解是深入编程的前提. 让我们一起来看看这些变态题到底变态不变态吧! 第1题 1 ["1", "2", "3"].map(parseInt) 知识点: Array/map Number/parseInt Global_Objects/parseInt

《数据库系统概论(第5版)》课后习答案 王珊、萨师煊编著版 课后题解析 高等教育出版社出版 答

<数据库系统概论(第5版)>课后习答案 王珊.萨师煊编著版 课后题解析 高等教育出版社出版 答案与解析 <数据库系统概论(第5版)> 王珊.萨师煊编著版 第二篇 第1章 课后答案与解析 完整答案在页面最下方 前言第一篇 基 础 篇 课后习题答案与解析第1章 绪论 课后习题答案与解析1.1 数据库系统概述1.2 数据模型1.3 数据库系统的结构1.4 数据库系统的组成1.5 小结习题本章参考文献第2章 关系数据库 课后习题答案与解析2.1 关系数据结构及形式化定义2.2 关系操作2.

nyist oj 311 全然背包 (动态规划经典题)

全然背包 时间限制:3000 ms  |  内存限制:65535 KB 难度:4 描写叙述 直接说题意,全然背包定义有N种物品和一个容量为V的背包.每种物品都有无限件可用.第i种物品的体积是c,价值是w. 求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大.本题要求是背包恰好装满背包时,求出最大价值总和是多少. 假设不能恰好装满背包,输出NO 输入 第一行: N 表示有多少组測试数据(N<7). 接下来每组測试数据的第一行有两个整数M.V. M表示物品种类的数目,V表示背

nyist oj 36 最长公共子序列 (动态规划基础题)

最长公共子序列 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 咱们就不拐弯抹角了,如题,需要你做的就是写一个程序,得出最长公共子序列. tip:最长公共子序列也称作最长公共子串(不要求连续),英文缩写为LCS(Longest Common Subsequence).其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列. 输入 第一行给出一个整数N(0<N<100)表示待测数据组数 接

OCP读书笔记(23) - 题库(ExamC)

200.Which operation requires that you create an auxiliary instance manually before executing the operation? (Choose all that apply.) A. Backup-based database duplication. B. Active database duplication. C. Tablespace point-in-time recovery. D. No ope