DP-线性DP的一些题目

1.最长上升子序列

活动 - AcWing

给定一个长度为n的数列,求数值严格单调递增的子序列的长度最长是多少。

解题

我们将上升子序列的倒数第二个数作为状态划分的依据,当其小于当前数时,长度可以+1,否则不行。

要记得最后要遍历一遍所有的f求最大值!因为不一定以a[n]结尾的上升子序列是最长的。

#include<iostream>
using namespace std;
const int N=1010;
int n,a[N],f[N];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<=i;j++)
        {
            if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
        }
    }
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[i]);
    printf("%d",res);
}

有一个O(nlogn)的做法,使用二分来优化。

在此前,每次进行状态转移时,我们需要遍历一遍所有可能的倒数第二个数,才能找出方案,但是其实,我们可以用一个数组存储每个长度的上升子序列末尾最小的值。我们可以很容易地知道,这个数组一定是单调递增的,因为当上升子序列的长度相同时,末尾最小的上升子序列,一定是我们最优的选择,如果当前上升子序列的长能够+1,则说明当前长度n+1的上升子序列末尾元素一定比上一个长度n的末尾元素大。

有了这个单调递增的性质,我们进行状态转移的时候,可以通过二分在数组中找出比当前值小的最大元素,把它作为上一个状态,从而进行状态转移。

#include<iostream>
using namespace std;
const int N=1010;
int a[N],q[N],n;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int len=0;
    q[0]=-2e9;
    for(int i=1;i<=n;i++)
    {
        int l=0,r=len;
        while(l<r)
        {
            int mid=(l+r+1)>>1;
            if(q[mid]<a[i]) l=mid;
            else r=mid-1;
        }
        len=max(len,r+1);
        q[r+1]=a[i];
    }
    printf("%d",len);
}

2.花店橱窗

登录—专业IT笔试面试备考平台_牛客网

小q和他的老婆小z最近开了一家花店,他们准备把店里最好看的花都摆在橱窗里。

但是他们有很多花瓶,每个花瓶都具有各自的特点,因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果。

为了使橱窗里的花摆放的最合适,他们得想个办法安排每种花的摆放位置。

可是因为小q和小z每天都太忙,没有时间设计橱窗里花的摆法,所以他们想让你帮他们求出花摆放的最大美观程度和每种花所放的位置。

每种花都有一个标识,假设杜鹃花的标识数为1,秋海棠的标识数为2,康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:

杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。

如果花瓶的数目大于花束的数目。则多余的花瓶必须空置,且每个花瓶中只能放一束花。

每种花放在不同的瓶子里会产生不同的美观程度,美观程度可能是正数也可能是负数。

为取得最大美观程度,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值,并求出每种花应该摆放的花瓶的编号。

解题

#include<iostream>
using namespace std;
const int N=110;
int F,V;
int a[N][N],f[N][N];
int order[N][N];
int main()
{
    scanf("%d%d",&F,&V);
    for(int i=1;i<=F;i++)
        for(int j=1;j<=V;j++)
            scanf("%d",&a[i][j]);//第i种花放在第j个瓶子里的美观值
    for(int i=1;i<=F;i++)
    {
        for(int j=i;j<=V;j++)
        {
            if(i==j) f[i][j]=f[i-1][j-1]+a[i][j];//初始化
            else f[i][j]=max(f[i-1][j-1]+a[i][j],f[i][j-1]);
        }  
    }
    printf("%d\n",f[F][V]);
    int n=F,m=V;
    int ans[N];
    while(n)
    {
        while(f[n][m]==f[n][m-1]) m--;//当前花瓶不选
        ans[n]=m;
        n--;
        m--;
    }
    for(int i=1;i<=F;i++) printf("%d ",ans[i]);
}

3.免费馅饼

登录—专业IT笔试面试备考平台_牛客网

SERKOI最新推出了一种叫做“免费馅饼”的游戏:游戏在一个舞台上进行。舞台的宽度为W格,天幕的高度为H格,游戏者占一格。开始时游戏者站在舞台的正中央,手里拿着一个托盘。下图为天幕的高度为4格时某一个时刻游戏者接馅饼的情景。

游戏开始后,从舞台天幕顶端的格子中不断出现馅饼并垂直下落。游戏者左右移动去接馅饼。游戏者每秒可以向左或向右移动一格或两格,也可以站在原地不动。
馅饼有很多种,游戏者事先根据自己的口味,对各种馅饼依次打了分。同时,在8-308电脑的遥控下,各种馅饼下落的速度也是不一样的,下落速度以格/秒为单位。
当馅饼在某一秒末恰好到达游戏者所在的格子中,游戏者就收集到了这块馅饼。
写一个程序,帮助我们的游戏者收集馅饼,使得所收集馅饼的分数之和最大。

解题

从题目中可以分析出,对我们有用的信息实际上是馅饼掉落到底部的时间和位置,我们以此作为状态转移的依据。

在进行输入处理时,我们用一个二维数组记录下第i秒落在第j个位置的馅饼的分值,在这一步,可以通过判断(H-1)%fa是否为0(是否能在整数秒下落到底部)来筛选掉一些不可能被接到的馅饼。

由于我们只知道起点位置start而不知道终点位置,我们可以由f[0][start]轻易地表示出结果,因此我们在进行状态转移时,应该从末状态转移到初状态,倒序遍历。

在输出行走路径时,我们首先在状态转移过程中记录下转移路径,然后从头开始遍历一遍输出就可以了。

#include<iostream>
using namespace std;
const int N=1e4+10;
int W,H;
int bing[N][N];
int f[N][N];
int main()
{
    scanf("%d%d",&W,&H);
    int i=0;
    int ti,st,fa,sc;
    int tt=0;
    while(cin>>ti>>st>>fa>>sc) 
    {
        if((H-1)%fa==0) 
        {
            bing[ti+(H-1)/fa][st]+=sc;
            tt=max(tt,ti+(H-1)/fa);
        }
    }
    
    int start=W/2+1;
    int l=-(W/2),r=(W/2);
    int path[N][N];
    
    for(int i=tt;i>=0;i--)
        for(int j=1;j<=W;j++)
            for(int k=-2;k<=2;k++)
            {
                if(j+k<=W) 
                {
                    if(f[i+1][j+k]+bing[i][j]>f[i][j]) 
                    {
                        f[i][j]=f[i+1][j+k]+bing[i][j];
                        path[i][j]=k;
                    }
                }
            }
    
    printf("%d\n",f[0][start]);
    for(int i=0;i<tt;i++) 
    {
        printf("%d\n",path[i][start]);
        start+=path[i][start];
    }
}

4.钉子和小球

登录—专业IT笔试面试备考平台_牛客网

有一个三角形木板,竖直立放,上面钉着n(n+1)/2颗钉子,还有(n+1)个格子(当n=5时如图1)。每颗钉子和周围的钉子的距离都等于d,每个格子的宽度也都等于d,且除了最左端和最右端的格子外每个格子都正对着最下面一排钉子的间隙。

让一个直径略小于d的小球中心正对着最上面的钉子在板上自由滚落,小球每碰到一个钉子都可能落向左边或右边(概率各1/2),且球的中心还会正对着下一颗将要碰上的钉子。例如图2就是小球一条可能的路径。

我们知道小球落在第i个格子中的概率pi=

 ,其中i为格子的编号,从左至右依次为0,1,...,n。

现在的问题是计算拔掉某些钉子后,小球落在编号为m的格子中的概率pm。假定最下面一排钉子不会被拔掉。例如图3是某些钉子被拔掉后小球一条可能的路径。

              图1                                  图2                              图3

解题

对我个人而言,本题的解题难点在于,我们要把概率问题,转换为小球个数问题。在每个结点,小球数量一分为二。

如果当前是*,则下一层能够到达的结点都可以加上f[i][j]个小球;如果当前是.,则f[i+2][j+1]的位置可以加上4*f[i][j]个小球。

最后计算格子中小球个数/总小球个数,就可以得到概率。

#include<iostream>
using namespace std;
const int N=55;

char a[N][N];
int n,m;
long long gcd(long long a,long long b){
    if(b==0)    return a;
    else   return gcd(b,a%b);
}
int main()
{
    long long f[N][2*N];
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        for(int j=0;j<=i;j++)
        {
            cin>>a[i][j];
        }
    
    f[0][0]=1;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(a[i][j]=='*')
            {
                f[i+1][j]+=f[i][j];
                f[i+1][j+1]+=f[i][j];
            }
            else if(a[i][j]=='.') f[i+2][j+1]+=4*f[i][j];
        }
    }
    long long sum=0;
    for(int i=0;i<=n;i++) sum+=f[n][i];
    long long res=f[n][m];
    long long g=gcd(res,sum);
    printf("%lld/%lld",res/g,sum/g);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值