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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章