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


    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{ egin{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} ight. ]

    然后根据讲稿:当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;
    }
    
  • 相关阅读:
    idea搭建Spring Boot+Mybatis及使用教程
    CentOS 6 安装配置JDK+tomcat环境
    关于无线路由器之间的连接
    关于链表的面试问题(判断一个单链表中是否有环)
    华为机试ACM(字符组合问题)
    Activity的启动模式
    Notification状态栏显示信息
    Java注释Override、Deprecated、SuppressWarnings
    Fragment
    Service
  • 原文地址:https://www.cnblogs.com/de-compass/p/11238157.html
Copyright © 2020-2023  润新知