DP训练专题(几道DP水题)

动态规划小练

昨天早上有道贪心题我妄想用dp来做,想了O(n)的算法想了巨久,然后没想出来草草打了一个n2的DP然后就十分难受。于是就去练了一下水DP以提升自己DP水平。

昨天那道dp

量化交易

【问题描述】
applepi训练了一个可以自动在股票市场进行量化交易的模型。通常来说,训练了一个可以自动在股票市场进行量化交易的模型。你懂得就好比一架印钞机。不过为了谨慎起见applepi还是想先检查一下模型的效果。
applpie收集了“塞帕思股份收集了“塞帕思股份(surpass)”在最近的连续”在最近的连续N天内的价格。在每一天内的价格。在每一天中,他可以做如下事情之一:
1. 睡(把)觉妹。睡(把)觉妹。
2. 以当天的价格作为成交买入1股“塞帕思”的票。
3. 以当天的价格作为成交卖出1股“塞帕思”的票。
最初applepi不持有该股票。现在你需要计算出最优策略下,不持有该股票。现在你需要计算出最优策略下,N天后applepi能够获得的最大利润。为了维护森林和平,本着清仓甩锅原则在N天的交易结束后applepi也不能持有“塞帕思”的股票。
【输入格式】
每个测试点包含若干组数据,以EOF结尾。对于每组数据:
第一行1个整数N。
第二行N个正整数,相邻两之间用1个空格隔开,表示每一天股票的价格。

一开始以为该题跟某经典dp美元换马克异曲同工,可它毕竟是二中的联考,于是草草打了一个n2DP

f[i][j]=max(f[i-1][j-1]-a[i],f[i-1][j+1]+a[i],f[i-1][j]);
f[i][j]表示前i天拥有j股票的最优利益,从上一轮买或者卖或者什么都不干过继过来,要注意的是数组一开始要置的很小,大概-9999999吧,如果不小会错,为此我调了好久,还不停质疑dp的正确性,哎。

#include<iostream>
#include<cstdio>
using namespace std;
int n,a[100005];
long long f[1005][1005];
#define INF 100000000000
int main()
{
    int k=0;
    while (cin>>n)
    {
        k++;
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        for (int i=-1;i<=n;i++) f[0][i]=-INF;
        f[0][0]=0;
        for (int i=1;i<=n;i++)
        for (int j=0;j<=n;j++)
        {
            if (j>i||j>n-i)
            {
                f[i][j]=-INF;
                continue;
            }
            f[i][j]=max(f[i-1][j-1]-a[i],f[i-1][j+1]+a[i]);
            f[i][j]=max(f[i][j],f[i-1][j]);
        }
//      for (int i=1;i<=n;i++)
//      {
//          for (int j=0;j<=n;j++) cout<<f[i][j]<<' ';
//          cout<<'\n';
//      }
        printf("Case #%d: %d\n",k,f[n][0]);
    }
}

因为这道题一开始想的是O(n)DP,所以是想维护两个数组,一个规划资金,另一个规划股票的利益但是我真的想不出来怎么保证最后把股票卖光而且不知道如何维护最优卖出日期,
万万没想到正解是……贪心+堆。。。用一个堆去做一个后悔的操作。
如果当前已经买了一股,那么就要卖到最大的一天.
->但是我们并不知道那一天最大.
->于是我们在能产生利润时就卖掉.
->但是我们需要一个“后悔”操作,表示那天不卖,在此时利润更大时再买.

于是开个小根堆表示已经买了的股票.
当输入小于堆顶说明产生不了利益,先买掉
当输入大于堆顶说明可以产生利润,于是卖掉,并且把当天股价加入堆中两次(可以达到“后悔”的效果)

下面是”后悔“操作的原理:
假设堆顶是第a天的报价,当前是第b的的报价,第a天买进的股票应该在第c天卖出,
第b天买进的股票需要在第d天卖出,
a

#include <bits/stdc++.h>
#define LL long long
using namespace std;
LL read(){
    LL x=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
const int MAXN=100010;
LL n,ans,x,temp;

priority_queue <LL,vector <LL>,greater<LL> > heap;
int main(){
    int cnt=1;
    while (scanf("%d",&n)!=EOF){
        ans=0;
        while (!heap.empty()) heap.pop();
        for (int i=1;i<=n;i++){
            x=read();
            if(heap.empty()||heap.top()>=x) heap.push(x);
            else{
                temp=heap.top();
                heap.pop();
                ans+=x-temp;
                heap.push(x);
                heap.push(x);
            }
        }
        printf("Case #%d: %lld\n",cnt++,ans);
    }
}

哦朋友再见再见
于是我去了某谷做了下历年的DP水题。。。

传纸条

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个 mm 行 nn 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1(1,1 ),小轩坐在矩阵的右下角,坐标 (m,n)(m,n) 。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 00 表示),可以用一个 0-1000−100 的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这 22 条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的 22 条路径。

我非常的伤心真道题一开始也没有想出来,但是大概知道是双线程,这道题等同于维护两条从起点到终点的最优路径。。之间做过方格取数。四维暴力DP但是这道题过不去……愚笨的我就不思变通。……想了巨久发现不对劲.。。
可以省一维。
无论如何两条路径的某一步,都会在同一条斜线上。

f[k][i][j]=max(max(f[k-1][i][j],f[k-1][i-1][j]),max(f[k-1][i][j-1],f[k-1][i-1][j-1]))+a[k-i][i]+a[k-j][j];

所以设个k表示走了几步,可以第一条路径走或者第二路走或者都不走或者都不走,反正把几种情况枚举完了就OK

#include<bits/stdc++.h>
using namespace std;
const int maxn=60;
int a[maxn][maxn];
int f[2*maxn][maxn][maxn];
int main()
{
  int m,n;
  scanf("%d%d",&m,&n);
  for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)
      scanf("%d",&a[i][j]);
  memset(f,-1,sizeof(f));
  f[2][1][1]=0;     
  for(int k=3;k<m+n;k++)
    for(int i=1;i<n;i++)
      for(int j=i+1;j<=n;j++)
      {
        int s=f[k][i][j];
        s=max(s,max(max(f[k-1][i][j],f[k-1][i-1][j]),max(f[k-1][i][j-1],f[k-1][i-1][j-1])));
        if (s==-1) continue; 
        f[k][i][j]=s+a[k-i][i]+a[k-j][j]; 
      }
  printf("%d",f[m+n-1][n-1][n]);
  return 0;
 }  

金明的预算方案

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 NN 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 00 个、 11 个或 22 个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 NN 元。于是,他把每件物品规定了一个重要度,分为 55 等:用整数 1-51−5 表示,第 55 等最重要。他还从因特网上查到了每件物品的价格(都是 1010 元的整数倍)。他希望在不超过 NN 元(可以等于 NN 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
请你帮助金明设计一个满足要求的购物单。
输入输出格式
输入格式:
第 11 行,为两个正整数,用一个空格隔开:
N mNm (其中 N(<32000)N(<32000) 表示总钱数, m(<60)m(<60) 为希望购买物品的个数。) 从第 22 行到第 m+1m+1 行,第 jj 行给出了编号为 j-1j−1 的物品的基本数据,每行有 33 个非负整数
v p qvpq (其中 vv 表示该物品的价格( v<10000v<10000 ),p表示该物品的重要度( 1-51−5 ), qq 表示该物品是主件还是附件。如果 q=0q=0 ,表示该物品为主件,如果 q>0q>0 ,表示该物品为附件, qq 是所属主件的编号)
输出格式:
一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <200000<200000 )。

因为格式问题,有些东西就不copy过来。。
乍一看有附带是不是很难?(大佬自行走开)
其实就是01背包变形,,
在算每一个有附带的物品时,把所有情况都写到,不选该物品,选该物品不选附带物品,只选附带一,只二,全附带。

if(j>=mw[i]+fw[i][1])f[j]=max(f[j],f[j-mw[i]-fw[i][1]]+mv[i]+fv[i][1]);   
        if(j>=mw[i]+fw[i][2])f[j]=max(f[j],f[j-mw[i]-fw[i][2]]+mv[i]+fv[i][2]);  
        if(j>=mw[i]+fw[i][1]+fw[i][2])  
        f[j]=max(f[j],f[j-mw[i]-fw[i][1]-fw[i][2]]+mv[i]+fv[i][1]+fv[i][2]);

写全了在检查一下,,就能过。

#include<iostream>  
using namespace std;  
int m,n,mw[33333],mv[33333],fw[33333][3],fv[33333][3],f[33333],v,p,q;  
//mw主件重量,mv主件价值,fw主件对应的附件重量,fv主副价值,n总重量,m总个数   
int main()  
{  
    cin>>n>>m;  
    for(int i=1;i<=m;i++){  
    cin>>v>>p>>q;  
    if(!q){  
        mw[i]=v; 
        mv[i]=v*p; 
    }  
    else{
        fw[q][0]++;
        fw[q][fw[q][0]]=v; 
        fv[q][fw[q][0]]=v*p;
    }  
    }  
    for(int i=1;i<=m;i++)  
    for(int j=n;j>=mw[i];j--){  
        f[j]=max(f[j],f[j-mw[i]]+mv[i]);   
        if(j>=mw[i]+fw[i][1])f[j]=max(f[j],f[j-mw[i]-fw[i][1]]+mv[i]+fv[i][1]);   
        if(j>=mw[i]+fw[i][2])f[j]=max(f[j],f[j-mw[i]-fw[i][2]]+mv[i]+fv[i][2]);  
        if(j>=mw[i]+fw[i][1]+fw[i][2])  
        f[j]=max(f[j],f[j-mw[i]-fw[i][1]-fw[i][2]]+mv[i]+fv[i][1]+fv[i][2]);  
    }    
    cout<<f[n]<<endl;  
    return 0;  
}  

然后今天的联考就舒服了不少。。

小象和老鼠

S 国的动物园是一个N*M的网格图,左上角的坐标是(1,1),右下角的坐标是(N,M)。小象在动物园的左上角,它想回到右下角的家里去睡觉,但是动物园中有一些老鼠,而小象又很害怕老鼠。动物园里的老鼠是彼此互不相同的。小象的害怕值定义为他回家的路径上可以看见的不同的老鼠的数量。若小象当前的位置为(x1,y1),小象可以看见老鼠,当且仅当老鼠的位置(x2,y2)满足|x1-x2|+|y1-y2|<=1 。由于小象很困了,所以小象只会走一条最近的路回家,即小象只会向下或者向右走。现在你需要帮小象确定一条回家的路线,使得小象的害怕值最小。
1<=N,M<=1000,0<=Aij<=100
Input
第一行包含两个用空格隔开的整数,N和M。
接下来一个N*M的矩阵表示动物园的地图。其中Aij表示第i行第j列上老鼠的数量。若Aij=0则表示当前位置上没有老鼠(小象的家里也可能存在老鼠)。
Output
输出一个整数,表示路线最小的害怕值是多少

这道题告诉我你以为你以为的就是正确的吗。。。
一开始非常开心以为碰到了水题,打了个非常普通的暴力没想到。。。
要判重,而且不是简单判重。。
于是第二遍的dp就是每走一步加上这一步能看到的所有老鼠然后减去这一格子里的老鼠。。。
哎,譬如先向下走再向右走,其实还重复了你最后落点的上一格的老鼠。。。
所以要分类讨论 ,讨论你上一格是如何来的。,然后不需要预处理能看到所有老鼠,在DP的时候每一个格子里直接算这一个能看到的老鼠。就不用加来减去啦。

f[i][j][0]=min(f[i][j-1][0]+a[i-1][j],f[i][j-1][1])+a[i+1][j]+a[i][j+1];
f[i][j][1]=min(f[i-1][j][0],f[i-1][j][1]+a[i][j-1])+a[i+1][j]+a[i][j+1];

#include<bits/stdc++.h>
using namespace std;

const int maxn=1010;

int a[maxn][maxn],f[maxn][maxn][2],n,m;
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
int main()
{
    n=read(); m=read();
    for (int i=1; i<=n; i++)
      for (int j=1; j<=m; j++)
        a[i][j]=read();
    memset(f,0x3f,sizeof(f));
    f[1][1][0]=f[1][1][1]=a[1][1]+a[1][2]+a[2][1];   
    for (int i=1; i<=n; i++)
      for (int j=1; j<=m; j++)
        {
          if(i==1&&j==1) continue; 
          f[i][j][0]=min(f[i][j-1][0]+a[i-1][j],f[i][j-1][1])+a[i+1][j]+a[i][j+1];
          f[i][j][1]=min(f[i-1][j][1]+a[i][j-1],f[i-1][j][0])+a[i+1][j]+a[i][j+1];
        } 
    cout<<min(f[n][m][0],f[n][m][1]);
}

猜你喜欢

转载自blog.csdn.net/beautiful_CXW/article/details/81264285
今日推荐