zoukankan      html  css  js  c++  java
  • 单调队列+线性dp题Watching Fireworks is Fun (CF372C)

    一、Watching Fireworks is Fun(紫题)

    题目:一个城镇有n个区域,从左到右1编号为n,每个区域之间距离1个单位距离节日中有m个烟火要放,给定放的地点ai,时间ti当时你在x,那么你可以获得∣bi-ai-x∣的幸福值,你每个单位时间可以移动不超过d个单位距离。你的初始位置是任意的(初始时刻为1),求你通过移动能获取到的最大的开心值。

    题目大意:有n个点,在这之中有m个点有烟花,分别会在t时刻燃放,每个烟花的幸福值是b。每一个单位时间内人可以走d个单位距离,起始点任意,在x位置观看a点的烟花可得到(a-abs(b-x))的幸福值,问最大幸福值是啥。

    ………………………………是不是有点看蒙了,不要慌,我们再借助样例来分析一下。

    输入格式:第一行三个数n,m,d(nmd就离谱),n,d<=150000,m<=300。

       接下来m行包括三个整数ai,bi,ti,ai<=150000,bi,ti<1e9,(保证ti>ti-1)。第i行表示第i个烟花。

    输出格式:输出一个整数 —— 从观看所有烟花获得的最大幸福总和。

    样例1:

    输入:

    50 3 1----------->一共50个点,有3个烟花,每一秒只能走1格

    49 1 1----------->第一个烟花在49位置,贴脸看幸福值为1,在第1秒燃放

    26 1 4----------->第二个烟花在26位置,贴脸看幸福值为1,在第4秒燃放

    6 1 10----------->第三个烟花在6位置,贴脸看幸福值为1,在第10秒燃放

    输出:

    -31(看到烟花了还不开心就离谱)

    一种可能的方案:

    第1秒在29位置(幸福值(1-(49-29)=-19)),第4秒到了26位置(幸福值(1-(26-26)=1)(1-19=-18)),第10秒到了20位置(幸福值(1-(20-6)=-13)(-18-13=-31))

    。。。分析完一个样例之后,是不是对题目大概清晰了些呢,那么我们可以开始讨论思路了

    思路:

    1.线性dp:f[i][j]表示第i个烟花燃放时处在j的最大幸福值,转移方程显然是f[i][j]=f[i-1][j-d*(t[i]-t[i-1])~j+d*(t[i]-t[i-1]));简单来说就是从可能转移到现在烟花的位置转移过来。

    2.根据上面的思路,我们的时间效率是m(枚举i)×n(枚举j)×n(枚举d)显然要超时,所以需要优化。

    3.我们注意到,在j从小到大枚举过程中,(H=d×(t[i]-t[i-1]))j-H~j+H的区间大小几乎是不变的,就像一个“窗口”从1到n扫过一样,这显然是单调队列的模型,所以我们可以使用其优化,可以把枚举d的那一层复杂度变成常数。

    具体操作:

    1.每遍历一个新的j的时候,我们枚举k从j到j+H(j-H当作限制用来出队),显然,k并不需要随着每一个j而从头遍历,只需记录一定区间(j-H~j+H)内的最大值即可,那么我们可以边读边扫,直到扫完整个区间1~n为止。

    2.利用单调队列的特性来处理区间最大值,在每进队一个新的k时候,比较一下它是否比队尾更优,如果如此,那么把队尾弹出,再把k入队即可(因为在“窗口”向右扫的过程中,靠右的点比它左边的点更晚失效(指<j-H),如果比队尾更优,那队尾这个点就没有存在的必要了),同时再遍历j的时候,处理一下队首元素是否已经“失效”,如果如此,也弹出。

    3.也许你注意到了,在刚才的过程中出现了“插入队尾”,“弹出队尾”,“弹出队首”三种操作,对于这样的操作,显然双端队列很适合处理。

    4.那么问题来了如何比较“更优”呢,我们知道,第i行的状态由i-1行转移过来,那么只要i-1行的第k个状态所对应的幸福值更大,他就优呗(奇怪的废话增加了)

    5.转移方程dp[i][j]=dp[i-1][q.front()];(根据单调队列的特性,队首元素是此区间内最大值)

    好了,思路弄懂了,就直接上代码。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int maxn=150000+10;
    LL f[500][maxn];
    int main(){
        int n,m,d,tt=0;
        scanf("%d%d%d",&n,&m,&d);
        for(int i=1;i<=m;i++){
            int a,b,t;
            scanf("%d%d%d",&a,&b,&t);
            LL H=1LL*d*(t-tt);
            tt=t;//这里并没有用t数组来保存两个烟花之间相差的时间,但这样操作也可以
            deque<int> q;
            int k=1;//k不随j的改变而重新遍历,所以在j循环之前定义
            for(int j=1;j<=n;j++){
                for(;k<=n&&k<=j+H;k++){//k的范围
                    while(!q.empty()&&f[i-1][k]>=f[i-1][q.back()])q.pop_back();
                    //如果k比队尾更优,队尾出队
                    q.push_back(k);//k入队
                }
                while(!q.empty()&&q.front()<j-H){
                    q.pop_front();
                }
                //每次遍历一个j,队首就可能失效一次
                f[i][j]=f[i-1][q.front()]+b-abs(a-j);
                //记录i,j情况下最大幸福值
            }
        }
        LL Max=-0x3f3f3f3f3f3f3f3f;
        for(int i=1;i<=n;i++){
            Max=max(Max,f[m][i]);
        }
        //找所有看完烟花的状态中(f[m][~])最大的那个,也就是最后站的地方
        printf("%lld",Max);
        return 0;
    }

    这里使用了#include<queue>中的双端队列,显然效率上要差一些,建议数组模拟

    下面附上数组模拟代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    using namespace std;
    typedef long long LL;
    const int maxn=150000+10;
    LL f[500][maxn];
    int main(){
        int n,m,d,tt=0;
        scanf("%d%d%d",&n,&m,&d);
        for(int i=1;i<=m;i++){
            int a,b,t;
            scanf("%d%d%d",&a,&b,&t);
            LL H=1LL*d*(t-tt);
            tt=t;
            LL qq[maxn],head=1,tail=0;
            int k=1;
            for(int j=1;j<=n;j++){
                for(;k<=n&&k<=j+H;k++){
                    while(head<=tail&&f[i-1][k]>=f[i-1][qq[tail]])tail--;
                    qq[++tail]=k;
                }
                while(head<=tail&&qq[head]<j-H)head++;
                f[i][j]=f[i-1][qq[head]]+b-abs(a-j);
            }
        }
        LL Max=-0x3f3f3f3f3f3f3f3f;
        for(int i=1;i<=n;i++){
            Max=max(Max,f[m][i]);
        }
        printf("%lld",Max);
        return 0;
    }

    这里还有一个小问题:在队首出栈时我们的while循环写在了k的循环的外面,这是为什么呢,会有影响吗。

    会有影响的!!!

    给你们一个错误样例,可以思考一下为什么(调试一下就知道了)

    100 2 5
    29 78 53
    78 13 60

  • 相关阅读:
    目标跟踪的评价指标
    [c++] stringstream的用法
    [OpenCV] sift demo
    [TCP/IP] 滑动窗口
    [python] 一行命令搭建http服务内网传文件
    YII 获取系统级请求参数的常用方法
    windows系统npm如何升级自身
    修补--Redis未授权访问漏洞
    MySQL中的两个时间函数,用来做两个时间之间的对比
    Centos 安装mongodb
  • 原文地址:https://www.cnblogs.com/liu-yi-tong/p/13199505.html
Copyright © 2011-2022 走看看