cqyz oj |【三校联考试题】龙珠 | DP动态规划 | 滑动窗口优化

时间:2021-02-13 11:08:00

Description

  你得到了一个龙珠雷达,它会告诉你龙珠出现的时间和地点。
  龙珠雷达的画面是一条水平的数轴,每一个窗口时间,数轴的某些点上会出现同一种龙珠,每当你获得其中一颗龙珠,其它龙珠就会消失。下一个窗口时间,数轴上又会出现另一种龙珠。总共有n个窗口时间,也就是总共有n种龙珠。
  假设你会瞬间移动,你从数轴的x点移动到y点,耗时0秒,但是需要耗费|x-y|的体力。同时,挖出一颗龙珠也需要耗费一定的体力。请问,最少耗费多少体力,就可以收集齐所有种类的龙珠。

Input

  第一行,三个整数n,m,x,表示共有n个窗口时间,每个窗口时间会出现m个龙珠,x是一开始你所处的位置。  接下来有两个n*m的矩阵。  对于第一个矩阵,坐标为(i,j)的数字表示第i个窗口时间,第j个龙珠的位置。  对于第二个矩阵,坐标为(i,j)的数字表示第i个窗口时间,挖取第j个龙珠所需的体力。

Output

  一个整数,表示所需最小体力

Sample Input 1

3 2 5
2 3
4 1
1 3
1 1
1 3
4 2

Sample Output 1

8

Hint

所有数据均为整数数轴范围在0到30000。挖一颗龙珠所需体力不超过30000。结果保证在int范围,对于50%的数据,1<=n<=50,1<=m<=500。对于100%的数据,1<=n<=50,1<=m<=5000。


按题意定义状态函数f(i,j)表示第i个窗口时间挖掉龙珠j花费体力的最小值
设结构体成员:
a[i][j].d=表示第 i 个时间窗口出现的第 j 个龙珠的位置。
a[i][j].t=表示第 i 个时间窗口挖第 j 个龙珠需要耗费的体力。
边界\(f(1,j)=|a[1][j].d-x|+a[1][j].t | 1<=j<=m\)
输出\(Ans=min\{f(n,j) | 1<=j<=m\}\)
转移方程\(f(i,j)=min\{f(i-1,k)+|a[i-1][k].d-a[i][j].k|\}+a[i][j].t | 1<=k<=m\)
写出代码,结束。

标题都写了要优化当然不可能不做。直接按这个方程枚举i,j,k的话时间复杂度\(O(n*m*m)=O(1.25*10^9)\)超时,但是根据写动归的经验,i,j肯定是要枚举的,时间只能从枚举k里面榨出来。

下面对方程进行变形,展开绝对值并且把只与i,j有关的项提出min的括号,留下与k有关的项:
\[ f(i,j)=min \left\{ \begin{array}{} min\{f(i-1,k)-a[i-1][k].d\}+ a[i][j].d+ a[i][j].t |1<=k<=m && a[i-1][k].d<=a[i][j].d \\ min\{f(i-1,k)+a[i-1][k].d\}- a[i][j].x+ a[i][j].t |1<=k<=m && a[i][k].d>a[i-1][j].d \\ \end{array} \right. \]
然后根据讲稿:当a[i-1][k].d<=a[i][j].d 时(从 a[i][j].d 的左边过来)该方程实质是在 a[i][j].d 的左边选择一个最小的 f(i-1,k)-a[i-1][k].d;由此可以用滑动窗口维护,只是这个窗口不断地向右延长,即 a[i-1][1]一直在窗口中。应每个时间窗口的龙珠信息按照 x 由小到大排序。

蒟蒻的我看不大懂,就按我自己的理解讲一遍。对于第一个方程,就是用单调队列维护满足a[i-1][k].d<=a[i][j].d(此时的入队条件)的k中,
f(i-1,k)-a[i-1][k].d(此时维护的单调值)的最小值。排序后可以保证若a[i-1][k].d>a[i][j].d,则k以后的元素也都不满足,可以直接退出循环,达到降低时间复杂度的目的。
最终:排序时间O(nmlog2m),计算时间O(nm),因此总时间复杂度为排序耗时O(n*mlog2m)

空间上也可以优化,因为方程只用到上一层来更新这一层,所以f数组可以只开2层滚动更新,代码里没有这样写。
上代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define inf 0x3f3f3f3f
#define maxn 55
#define maxm 5005
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
#define _per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long ll;
int n,m,x;
int f[maxn][maxm];
struct data{
    int t,d;
    bool operator<(const data &b)const{return d<b.d;}
}a[maxn][maxm];
int q[maxm],qf,qr,h[maxm],hf,hr;
int main(){
    scanf("%d%d%d",&n,&m,&x);
    _rep(i,1,n)_rep(j,1,m)scanf("%d", &a[i][j].d);
    _rep(i,1,n)_rep(j,1,m)scanf("%d", &a[i][j].t);
    _rep(i,1,n)sort(a[i]+1,a[i]+1+m);
    _rep(j,1,m)f[1][j] = abs(a[1][j].d-x) + a[1][j].t;
    
    _rep(i,2,n){
        int k=1;
        qf=qr=hf=hr=0;
        q[qr++]=h[hr++]=inf;        //细节:没有这一步则要在给f[i][j]赋值时判断队列是否为空
        _rep(j,1,m){
            while(k<=m && a[i-1][k].d<=a[i][j].d){//扫描a[i][j].d左边还未进队的点
                int tmp = f[i-1][k] - a[i-1][k].d;
                while(qf<qr && q[qr-1]>tmp)qr--;
                q[qr++] = tmp;
                k++;
            }
            f[i][j] = q[qf] + a[i][j].d + a[i][j].t;
        }
                
        k=m;
        _per(j,m,1){                //条件是a[i-1][k].d>a[i][j].d,所以应该从大到小算
            while(k>=1 && a[i-1][k].d>a[i][j].d){
                int tmp = f[i-1][k] + a[i-1][k].d;
                while(hf<hr && h[hr-1]>tmp)hr--;
                h[hr++] = tmp;
                k--;
            }
            f[i][j] = min(f[i][j], h[hf] - a[i][j].d + a[i][j].t);
        }
    }
    
    int ans=inf;
    _rep(j,1,m)ans=min(ans,f[n][j]);
    printf("%d",ans);
    return 0;
}