跳棋(jump)

跳棋(jump)

【題目描述】

小明迷戀上了一個新的跳棋遊戲,遊戲規則如下:棋盤是一排從0開始,順序編號的格子,遊戲開始時你位於0號格子,你每次只能往編號大的格子跳,而且你每次至少需要跳過L個格子,至多隻能跳過R個格子。每個格子都有一個給定的傷害值,顯然你希望得到的傷害值越少越好。
你能告訴小明他當他跳到最後一個格子時受到的累積傷害值最小爲多少嗎?
如果無論如何小明都無法跳到最後一個格子,這個時候你需要輸出”-1”。

注:從i號格子跳過x個格子表示從i號格子跳到第i+x+1號格子。

這是第三題??
這麼簡單的題目??
然後我打下了動規程序。。
測完樣例全對的我發現了數據範圍。。
這麼大的數據??
不過這是第3題,騙分就好。
我計算了能拿的分數:65%+15%=80%
哇,這麼多,太棒了耶。

dp[i]代表到達第i個位置有多少種方案
那麼不難想,只要暴掃將所有到達dp[i]方法取最小傷害值即可 核心代碼可想而知:

for(i=0;i<=n-L-1;i++)   
    for(j=L;j<=min(R,n-i-1);j++)
        f[i+j+1]=min(f[i+j+1],f[i]+a[i+j+1]);

用了上述方法注意f[1]到f[n]要變成很大的數,千萬不要動f[0],因爲f[0]本身就是0

如果你爲了顯示你的大佬風範,硬是想反着來也可以我就是反着做的,代碼如下:

for(int i=1;i<=n;i++)
    {
        f[i]=INF;
        for(int j=max(i-R,1);j<=max(i-L,0);j++) 
            f[i]=min(f[i],f[j-1]+a[i]);
    }

這種做法不用提前預處理,INF要自己開#define定義

格式如下 #define INF number(想要定義的數字) 放在int main外面
那麼就附上80代碼:

方法一:

#include<iostream>
#include<cstdio>
#define INF 999999999999
using namespace std;
int get_in()
{
    char ch=getchar(); int x=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-48; ch=getchar();}
    return x;
}
long long f[1000010];
int n,a[1000010],L,R;
int main()
{
    n=get_in(),L=get_in(),R=get_in();
    for(int i=1;i<=n;i++) a[i]=get_in(),f[i]=INF;
    for(int i=0;i<=n-L-1;i++)   
        for(int j=L;j<=min(R,n-i-1);j++)
            f[i+j+1]=min(f[i+j+1],f[i]+a[i+j+1]);
    if(f[n]==INF) {cout<<"-1"; return 0;}
    else cout<<f[n];
    return 0;
}

方法二:

#include<iostream>
#include<cstdio>
#define INF 999999999999
using namespace std;
int get_in()
{
    char ch=getchar(); int x=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-48; ch=getchar();}
    return x;
}
long long f[1000010];
int a[1000010],L,R,n;
int main()
{
    n=get_in(),L=get_in(),R=get_in();
    for(int i=1;i<=n;i++) a[i]=get_in();
    for(int i=1;i<=n;i++)
    {
        f[i]=INF;
        for(int j=max(i-R,1);j<=max(i-L,0);j++) 
            f[i]=min(f[i],f[j-1]+a[i]);
    }
    if(f[n]==INF) {cout<<"-1"; return 0;}
    else cout<<f[n];
    return 0;
}

我就不高興了鴨,
憑什麼這就不能AC
就只有80分數據太水,不然卡死你
早上不是講過了單調隊列嗎,你怎麼忘記了:

其實這就是用單調隊列維護dp
這道題裏我們用單調隊列來保證dp取隊頭元素是最優的方案

這裏的隊列並不是直接取傷害的值,而是存着地址
我們可以通過q(隊列)中的元素來找到dp中的元素
那麼我們認爲q[h]存着最低傷害的方案
則有了假的動態轉移方程:if(h<=t) dp[i]=dp[q[h]]+a[i];

同時爲了保證dp[i]取q中元素是合法的,也就是說,q中的元素必須在i-R-1以內!,不然死活跳不到i上面

所以在最前面要加上這一句:while(h<=t&&q[h]<i-R-1) ++h;

同時爲了保持隊列的單調性,必須要把最優方案推到最前面:if(i-L-1>=0&&dp[i-L-1]!=-1){while(h<=t&&dp[q[t]]>dp[i-L-1]) --t;q[++t]=i-L-1;}

if(i-L-1>=0&&dp[i-L-1]!=-1)這句if判斷是爲了確保dp[i-L-1]是有方案的,這樣纔有可能成爲新一輪的最優方案.

while(h<=t&&dp[q[t]]>dp[i-L-1]) --t;是爲了把新加入隊列的元素推到合適它的位置,隨着i的推進,這也許會成爲dp[?]的最優方案.

那麼q[++t]=i-L-1;就是把新的方案推入q裏面,等待下一輪的維護,這個時候q[h]已經確定了

那麼我又懵逼了??
怎麼說明單調隊列的正確性呢?? 很簡單了啦:
如果進行到k輪的隊列有這幾個元素{x1,x2,x3}而且已經經過了上面的處理
那麼從dp[x1],dp[x2],dp[x3]的位置跳到dp[k]都是可以的
這個時候因爲這個隊列是單調的(爲了維護dp[?]的最優方案)(千萬不要搞混:{6,7,5}可能也是單調的,因爲在本題是維護dp,所以只要dp[6]<dp[7]<dp[5]就行了)
所以x1方案的傷害最小,必定是最優方案即q[h]是最優的
q[++t]=i-L-1也必定是正確的,如果dp[i-L-1]在k輪是最優的,那麼因爲while(h<=t&&dp[q[t]]>dp[i-L-1]) --t; 這句話,隊列會被清空,那麼新加入隊列的i-L-1會成爲隊頭。如果在本輪並非最優,那麼對於k+?輪來說,舊的隊頭被淘汰,這就可能會成爲了一種最優方案。
所以說不管怎麼樣,隊頭確保是最優的!!!

那麼附上AC代碼:

#include<iostream>
#include<cstdio>
using namespace std;
int n,L,R,h=1,t,a[1000010],q[10000010];
long int dp[1000010];
int get_in()
{
    char ch=getchar(); int x=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-48; ch=getchar();}
    return x;
}
int main()
{
    n=get_in(),L=get_in(),R=get_in();
    for(int i=1;i<=n;i++) a[i]=get_in();
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<=n;++i)
    {
        while(h<=t&&q[h]<i-R-1) ++h;
        if(i-L-1>=0&&dp[i-L-1]!=-1)
        {
            while(h<=t&&dp[q[t]]>dp[i-L-1]) --t;
            q[++t]=i-L-1;
        }
        if(h<=t) dp[i]=dp[q[h]]+a[i];
    }
    printf("%d",dp[n]);
    return 0;
} 

注意:h>t纔是隊列清空的狀態,初始h=t+1

我不知道被這個細節卡了多久,QAQ

那麼介紹一種高級的東西!我查了百度哦。
deque<—高級東西,用起來 方便 還行
可以認爲這是一種雙頭的隊列,兩邊都可以進出
彈出隊頭—> q.pop_front() 相當於++h
彈出隊尾—> q.pop_back() 相當於–t
壓入隊頭—> q.push_front() 相當於q[–h]=?
壓入隊尾—> q.push_back() 相當於q[++t]=?
取出對頭—> q.front() 相當於q[h]
取出隊尾—> q.back() 相當於q[t]
隊列長度—> q.size() 相當於t-h+1;
那麼AC代碼(標程):

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;
deque < int > Q;
int n, a[1000001], dp[1000001], l, r;
int main()
{
    scanf("%d%d%d",&n,&l,&r);
    for (int i = 1; i <= n; i++) scanf("%d",&a[i]);
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    for (int i = 1; i <= n; i++)
    {
        while(Q.size()&&Q.front()<i-r-1) Q.pop_front();
        if(i-l-1>=0&&dp[i-l-1]!=-1)
        {
            while(Q.size()&&dp[Q.back()]>dp[i-l-1]) Q.pop_back(); 
            Q.push_back(i-l-1);
        }
        if(Q.size()) dp[i]=dp[Q.front()]+a[i];
    }
    printf("%d",dp[n]);
    return 0;
}

蒟蒻的註解,QAQ,看看就行啦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章