*点击

[ACOI2020] 课后期末考试滑溜滑溜补习班(单调队列优化dp)

题目

Description

潮田 渚(Shiota Nagisa)因为理科不大好,自然会被仔细观察学生的杀老师发现,于是渚同学只得加入杀老师举办的课后期末考试滑溜滑溜补习班。至于为什么叫这个名字,额,你不能问我啊。

 

在补习班上,因为多个学生会同时有需求,所以杀老师会制造分身用音速移动来回回答问题。

补习班上有 n 个同学,他们每一个人都有一个问题。杀老师为了有序回答学生的问题,把所有学生排成了一列。第 i 个学生的问题有一个困难值 ai,杀老师回答第 i 个学生的问题需要花费 ai 的精力。杀老师到了哪里,它就要解决那个学生的问题。杀老师最开始会解决序列中第一个同学的问题,他最后会去解决最后一个同学的问题。

杀老师每次解决完一个同学的问题到下一个同学的座位上就要花费 kk 点精力值。特殊的,如果杀老师想让自己轻松一点,可以不移动到下一个,可以直接到下两个,下三个,就不用解决跳过的同学的问题了。对应的,它会被学生调侃。受到打击的杀老师自然会花费格外的精力,花费的精力为 k+(q-p-1) \times d当前位置为 p,跳到的位置为 q)。

当然的,杀老师也是有速度的啊,并且它想解决学生的一些问题,所以说杀老师最多只会跳过 x-1个学生,去解决下 x 个学生的问题。

Input

  • 第一行五个整数 n,k,d,x,tpn,k,d,x,tp,表示有 nn 个学生,只按顺序去到下一个学生的座位需要花费 kk 点精力,每多跳过一个学生就要多花费 dd 点精力值,每一次最多只能跳过 x-1x1 个学生,是否是特殊数据。

  • tp=0,第二行 nn 个整数 a_{1\dots n}a1na_iai 表示第 ii 个学生的问题的困难值为 a_iai
  • tp=1,第二行一个整数 SeedSeed,作为种子,然后调用 rndrnd 函数依次生成 nn 个整数,作为 aa 数组,a_iai 表示第 ii 个学生的问题的困难值为 a_iai
inline int rnd () {
    static const int MOD = 1e9;
    return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}

Output

一行一个整数,表示杀老师解决完最后一个同学的问题最少需要花费多少精力。

Sample Input1

5 3 4 1 0
1 2 3 4 5

Sample Output1

27

 

Sample Input2

10 30630 56910 2 0
7484 99194 86969 17540 29184 68691 91892 81564 93999 74280

Sample Output2

717318

Sample Input3

10000000 899999999 923456655 213111 1
1314520

Sample Output3

9231813656566921

Hint

样例1:杀老师每次不能跳过学生,因此他必须依次移动并解决所有问题,故答案为解决问题所需的精力 1+2+3+4+5=151+2+3+4+5=15 与移动所需的精力 4 \times 3=124×3=12,所以花费精力之和为 2727。


思路

首先很明显是一道动态规划的题;

我们设 $dp[i]$ 表示解决到第 $i$ 个同学的问题,需要花费的最小精力;

那么 $dp$ 转移方程就是

$dp[i]=min(dp[i],dp[j]+a[i]+k+(i-j-1)*d) $ ;

方程意思是从解决了第 $j$ 个同学到解决第 $i$ 个同学的问题所消耗的精力;

$a[i]$ 是第$i$ 个问题解决需要的精力 ,$(i-j-1)*d$ 表示跳过同学被吐槽消耗的精力;

具体跳过消耗的精力题目以给出求法,请仔细看题!!!;

那么我们很惊奇的发现:

方程可以转化一下,变成:

$dp[i]=min(dp[i],dp[j]-j*d+a[i]+i*d+k-d) $ ; (把 $ (i-j-1) \times d$ 分配开);

那么方程求最小值就与$j$ 有关了;

我们就可以把 $j$ 放到一个单调队列里,每次找一个 $min(dp[j]-j*d)$ ;

那么什么时候踢队头呢,题目限制跳过的同学不能超过 $x-1$ ;

所以如果 $i-q[head]-1~>~x-1$ 就要踢队头;

这样就 $ok$ 了,$AC$ 了;

 

代码

#include<bits/stdc++.h>
#define re register//上面为啥没有注释 
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}
ll n,k,d,x,tp;
ll Seed;
ll a[10000010];
ll dp[10000010];
ll q[10000010];
inline ll rnd () 
{
    static const ll MOD = 1e9;
    return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}//题目中给的生成每个 a[i] 
int main()
{
    memset(dp,127/3,sizeof(dp));//初始 
    n=read(); k=read(); d=read(); x=read(); tp=read();//读入 
    if(tp==0)//读入 a[i] 
    {
        for(re ll i=1;i<=n;i++)
            a[i]=read();
    }
    else//生成 a[i] 
    {
        Seed=read();
        for(re ll i=1;i<=n;i++)
            a[i]=rnd();
    }
    dp[0]=0; dp[1]=a[1];//初始化,题目要求第一个同学的必须解决 
    ll head=1,tail=0;// 
    for(re ll i=1;i<=n;i++)
    {
        while(head<=tail&&i-q[head]-1>x-1)//跳过的同学超过限制 
            head++;//踢队头 
        if(i-q[head]-1>=0)//状态转移方程 
            dp[i]=min(dp[i],dp[q[head]]+a[i]+k+(i-q[head]-1)*d);
        while(head<=tail&&dp[q[tail]]-q[tail]*d>dp[i]-i*d)//寻找最小的 dp[j]-j*d)
            tail--;//踢队尾 
        q[++tail]=i;//入队 
    }
    printf("%lld\n",dp[n]);//输出 
    return 0;//AC 
}
posted @ 2020-08-13 22:38  木偶人-怪咖  阅读(158)  评论(0编辑  收藏  举报
*访客位置3D地图 *目录