1. 程式人生 > >PJ可能會用到的動態規劃選講-學習筆記

PJ可能會用到的動態規劃選講-學習筆記

.com con bool 每一個 除了 數組a 二分查找 就是 空間

PJ可能會用到的動態規劃選講-學習筆記

by Pleiades_Antares

難度和速度全部都是按照普及組來定的咯
數位狀壓啥就先不講了
這裏主要提到的都是比較簡單的DP

一道思維數學巧題(補昨天)

先看一道外文題目:
(簡單翻譯稍微改了下,原題目戳我)

現在PA要放技能,要放n(10e9)個技能,
但是放技能有冷卻時間,x秒才能放一次。
PA有兩個事情可以做:

  1. 有m個天賦可以學習,第i個天賦要花b[i]塊錢,作用是把冷卻時間改為a[i]。
  2. 可以找個打手,有k個打手可以找,請第i個打手需要花掉d[i]塊錢,他會直接幫你放c[i]次技能。

m, k 10e5

給出初始金錢數,問所用的最少時間。

註意天賦最多只能學習一個,打手最多只能請一位。

拿到題目以後,我們首先要想:這道題用什麽方法來做?(比如貪心,dp等等)

首先:
要枚舉選擇學哪個天賦 (此時我們還剩下C塊錢)
然後下一步??找到用哪個打手比較好。
技術分享圖片
看這張圖,貪心顯然是不行的。
那當我們枚舉完以後怎麽才能選擇打手呢?

——什麽情況下我們絕對不會選擇x這個打手
——當且僅當:有人比x便宜且比x能幹活

啥時候可以貪心呢?
技術分享圖片

我們把那種又貴又不好用“名不副實”的打手給排除掉
直接對打手按照花費排序,然後如果某個打手前面有(說明比他便宜)比他能幹的,那麽就說明這個人名不副實。
這樣排除了以後,單調的時候,??打手越貴就越好用
這個時候我們再選擇自己能力範圍內能夠買到的最貴的打手就可以了哇

此時處理非常容易,二分查找就直接ok

ll les(ll p)
{
    // printf("you can use %lld to less %lld\n",(*--upper_bound(ok.begin(),ok.end(),hero(0,p))).d,(*--upper_bound(ok.begin(),ok.end(),hero(0,p))).c);
    return (*(--upper_bound(ok.begin(),ok.end(),hero(0,p)))).c;
}

上面為二分查找orz rxz大佬寫的我們這種蒟蒻就好好寫正常的二分查找好了qwq
rxz大佬代碼:

#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;

ll a[100005],b[100005];
ll n,m,k,x,s;

struct hero
{
    ll c,d;
    friend bool operator < (hero a,hero b)
    {
        return a.d<b.d;
    }
    hero(ll _c=0,ll _d=0) {c=_c,d=_d;}
};
hero h[100005];
vector <hero> ok;

void inp()
{
    ll i;

    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(h,0,sizeof(h));
    ok.clear();

    scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&x,&s);

    for(i=1;i<=m;i++)
        scanf("%lld",&a[i]);
    for(i=1;i<=m;i++)
        scanf("%lld",&b[i]);
    for(i=1;i<=k;i++)
        scanf("%lld",&h[i].c);
    for(i=1;i<=k;i++)
        scanf("%lld",&h[i].d);
}

void gao()
{
    ll i,now=0;

    sort(h+1,h+1+k);

    ok.push_back(hero(0,0));

    for(i=1;i<=k;i++)
    {
        if(h[i].c<=now) continue;
        now=max(now,h[i].c);

        ok.push_back(h[i]);
    }
}

ll les(ll p)
{
    // printf("you can use %lld to less %lld\n",(*--upper_bound(ok.begin(),ok.end(),hero(0,p))).d,(*--upper_bound(ok.begin(),ok.end(),hero(0,p))).c);
    return (*(--upper_bound(ok.begin(),ok.end(),hero(0,p)))).c;
}

ll calc()
{
    ll i,t,ans=9000000000000000233;

    a[0]=x,b[0]=0;

    for(i=0;i<=m;i++)  //use talent i
        if(s>=b[i])
        {
            if(n-les(s-b[i])>0) t=(n-les(s-b[i]))*a[i];
            else t=0;
            // printf("Use talent %lld t=%lld\n",i,t);

            ans=min(ans,t);
        }

    printf("%lld\n",ans);
}

void work()
{
    inp();
    gao();
    calc();
}

int main(void)
{
    ll t;
    scanf("%lld",&t);

    while(t--)
        work();

    return 0;
}

DP選講

DP是啥?

簡單來說——哲學三連

我是誰? 【設計狀態】
我從哪裏來? 【從之前狀態設計轉移】
我到哪裏去? 【向之後狀態設計轉移】

熱身:鋼條切割
鋼條長度為n,可以切割。
最後,對於每個長度k,有對應價值w[k]。
問切割結束以後,所有鋼條的價值和最大是多少。
輔助理解靈魂繪圖:
技術分享圖片

首先,按照哲學三連來分析一下。
狀態:dp[i]表示在i剁了一刀,[1,i]這段區間產生的最大價值。
從哪裏來:對於所有的k,dp[i-k]都能到達dp[i]
轉移方程:dp[i]=max{dp[i-k+w[k]}

一些歷年的NOIP普及組題目講解

題表見此:
技術分享圖片

P1002:過河卒

狀態: dp[i][j]表示路徑數
dp[i][j]可以從dp[i-1][j],dp[i][j-1]這裏來
轉移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1]
考慮馬
被馬控制的點為0(不可能走到)
寫一個特判:如果被控制那麽就是0,否則就是剛才的那個轉移方程。

過河卒屬於是非常非常容易的一個dp,在考場上一定要能寫出來。

P1048 采藥

設計狀態
dp[v]表示容量為v,裝的最大價值 (有後效性)(無後效性是指:某階段的狀態一旦確定,則此後過程的演變不再受此前各狀態及決策的影響)
如果一個東西有後效性,把這個東西放進dp數組的下標往往可以消除後效性。
dp[k][v] 用時為v,考慮[1,k]的藥。這樣就可以排除其後效性。

我是誰:
dp[k][t]考慮了前k個藥,用時恰好為t,所取得的最大價值。
我從哪裏來:
1??k這個藥沒有采 dp[k-1][t]
2??k這個藥采了 dp[k-1][t-a[k]]+w[k] 之前用掉的時間就是t-a[k],(用a[k]的時間來采這個藥)(付出了時間得到了價值)(
(3??dp[k][t-1]來)
然後取一個max就做出來題目了。

單調隊列

給一個數組a和一個正整數k
對於每一個長度為k的區間,求出這個區間內的最大值。
技術分享圖片

技術分享圖片
——如果一個選手比你小,還比你強,那你就打不過他了。(比如說洛谷新一代吉祥物chen_zhe先生)
技術分享圖片

技術分享圖片


技術分享圖片

## 題目

Dice Game

// https://vjudge.net/problem/Gym-101502D

#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

// dp[n][k] : score=n , k is up

int dp[10005][7];

void move(int n,int k)
{
    int ans,i,j;

    for(i=1;i<=6;i++)
        if(i!=k && i+k!=7)
            dp[n+i][i]=min(dp[n+i][i],dp[n][k]+1);
}

void calc()
{
    int i,j;

    for(i=0;i<=10002;i++)
        for(j=1;j<=6;j++)
            dp[i][j]=23333333;

    dp[0][1]=0;

    for(i=0;i<=10002;i++)
        for(j=1;j<=6;j++)
            move(i,j);
}

void get()
{
    int x,i,ans=23333333;
    scanf("%d",&x);
    for(i=1;i<=6;i++)
        ans=min(ans,dp[x][i]);
    printf("%d\n",ans==23333333?-1:ans);
}

int main(void)
{
    int t;
    calc();

    scanf("%d",&t);
    while(t--)
        get();

    return 0;
}

考前訓練(復習)指南

技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
技術分享圖片
只要你開的空間爆了不管你有沒有用那麽多都是一個死

PJ可能會用到的動態規劃選講-學習筆記