zoukankan      html  css  js  c++  java
  • [ACOI2020] 课后期末考试滑溜滑溜补习班(单调队列优化dp)

    题目

    Description

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

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

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

    杀老师每次解决完一个同学的问题到下一个同学的座位上就要花费 kk 点精力值。特殊的,如果杀老师想让自己轻松一点,可以不移动到下一个,可以直接到下两个,下三个,就不用解决跳过的同学的问题了。对应的,它会被学生调侃。受到打击的杀老师自然会花费格外的精力,花费的精力为 k+(q-p-1) imes 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_{1dots 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 imes 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) imes 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
    ",dp[n]);//输出 
        return 0;//AC 
    }
  • 相关阅读:
    我看这篇对初学者很有帮助就转载了 Web.config (转载)
    自动开关机
    想换个地方了
    浅析软件项目管理中十个误区
    如何结束测试员和程序员之间的战争
    程序员四大忌 你该如何避免?(2006.01.13 来自:希赛)
    完整的变更请求管理解决方案
    情人节,我加班
    需求分析,你呀你!(源自Linuxaid.com.cn )
    转贴一篇对技术人员的十大忠告,希望新的一年有所提升!
  • 原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13498868.html
Copyright © 2011-2022 走看看