跳棋(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,看看就行啦

发布了80 篇原创文章 · 获赞 58 · 访问量 2514

猜你喜欢

转载自blog.csdn.net/bigwinner888/article/details/104541115