东方幻想乡系列模拟赛T2琪露诺题解

题目地址:

http://fzoj.xndxfz.com/JudgeOnline/problem.php?id=2146&csrf=17peCLMJM49glhqlg9KaD7Agf5o5QY87

题目描述:

在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。小河可以看作一列格子依次编号为0到N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子i时,她只会移动到i+L到i+R中的一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。每一个格子都有一个冰冻指数A[i],编号为0的格子冰冻指数为0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数A[i]。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。开始时,琪露诺在编号0的格子上,只要她下一步的位置编号大于N就算到达对岸。

输入格式:

第 1 行: 3 个正整数 N, L, R
第 2 行: N+1 个整数,第 i 个数表示编号为 i-1 的格子的冰冻指数 A[i-1]

输出格式:

第 1 行:一个整数,表示最大冰冻指数。保证不超过 2^31-1
第 2 行:空格分开的若干个整数,表示琪露诺前进的路线,最后输出-1 表示到达对岸

输入样例:

5 2 3
0 12 3 11 7 -2

输出样例:

11
0 3 -1

数据范围:

对于 60%的数据: N <= 10,000
对于 100%的数据: N <= 200,000
对于所有数据 -1,000 <= A[i] <= 1,000 且 1 <= L <= R <= N

题解:

题目大意我就不多说了,看到这道题可以想到DP,状态转移方程式也不难想出:
f[j]=max(f[k]+a[j],f[j])(j-r<=k<=j-l)
因为需要求一个区间中最大的点,如果选择N的时间扫一遍肯定会爆,所以我们需要使用线段树来维护一个区间中的最大节点的值。大概就这样,有一点需要注意的是数组的范围至少要比N的最大值大6000,不然最后一组数据会爆。下面贴代码吧。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls l,mid,rt<<1
#define rs mid+1,r,rt<<1|1
using namespace std;
const int N=200005;
int tree[N<<2],n,l,r,f[N<<1],a[N<<1];
void pushup(int rt)
{
    tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
} 
void build(int k,int num,int l,int r,int rt)
{
    if(l==r&&l==k)
    {
        tree[rt]=num;
        return ;
    }
    int mid=(l+r)>>1;
    if(k<=mid) build(k,num,ls);
    else build(k,num,rs);
    pushup(rt);
}
void Init()
{
    memset(f,0,sizeof(f));
    memset(tree,0xaf,sizeof(tree));
    memset(a,0,sizeof(a));
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
}
int query(int le,int ri,int l,int r,int rt)
{
    if(le<=l&&ri>=r)
    {
        return tree[rt];
    }
    int mid=(l+r)>>1;
    if(ri<=mid)
    {
        return query(le,ri,ls);
    }
    if(le>mid)
    {
        return query(le,ri,rs);
    }
    if(le<=mid&&ri>mid)
    {
        return max(query(le,mid,ls),query(mid+1,ri,rs));
    }
}
void dfs(int k)
{

    if(k==0)
    {
        printf("0 ");
        return ;
    }
    for(int i=max(k-r,0);i<=k-l;i++)
    {
        if(f[i]+a[k]==f[k])
        {
            int t1=a[i],t2=f[i];
            dfs(i);

            break;
        }
    }
    if(k>=n)
    {
        printf("-1");
        return ;
    }
    printf("%d ",k);
    return ;
}
void work()
{
    f[0]=a[0];
    build(0,f[0],0,n+r,1);
    for(int i=l;i<=n+r;i++)
    {
        f[i]=query(max(0,i-r),i-l,0,n+r,1)+a[i];
        build(i,f[i],0,n+r,1);
    }
    int maxn=0,ord;
    for(int i=n;i<=n+r;i++)
    {
        if(f[i]>maxn)
        {
            maxn=f[i];
            ord=i;
        }
    }
    printf("%d\n",maxn);
    dfs(ord);
}
int main()
{

    Init();
    work();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值