• BZOJ 1597 斜率优化


    题目链接: http://www.lydsy.com/JudgeOnline/problem.php?id=1597


     作为一个非权限狗,我抱着侥幸心理在BZOJ上搜了一发USACO,结果发现了这道题。。然后想出了一个DP,然而超时。。然后就百度一下,于是被安利了一个DP的经典优化技巧——斜率优化

     

    完整题解如下:

      注意到有一些土地是可以被其他土地无代价地“带掉”的。设第i块土地的长宽分别为a[i]b[i],则“无用”的土地i满足a[i]<=a[j] && b[i]<=b[j]。怎么判断呢?当然是排序。以a为第一关键字、b为第二关键字降序排序。接着维护一个单调队列。如果队尾的b小于等于队首的b,则将队尾指针+1。仅是每个单调队列的队首元素才是我们需要保留的。

      删去无用的边之后,由于a是降序排好的,所以一个显然的结论是b是升序的。这样一来,合并在一起买的土地一定是队列中的连续区间。

      锵锵锵!DP方程很好写啦~ f[i]为买前i块地(队列中的顺序)的最小花费,则有

    f[i]=min{ f[j]+ a[j+1]*b[i] | 0<=j<i }

          但这个是O(n^2)的,会超时。下面就用斜率优化。设在当前的状态f[i]时,从f[j]转移比f[k]优。那么

                                     f[j]+a[j+1]*b[i]<f[k]+a[k+1]*b[i]  ==>  b[i]<(f[k]-f[j])/(a[j+1]-a[k+1])
          说明,如果jk满足上述要求,k可以无视了,因为j一定比k更优。什么意思呢?再强调一次:b是不下降的序列!

             d(j,k)=(f[k]-f[j])/(a[j+1]-a[k+1]),当前的bb[now],则若i<j<kd(j,k)<d(i,k),那么j就是一个无用决策。这就满足了单调性,维护一个单调队列即可。因此,DP的时间复杂度减为O(n)。整个算法的时间复杂度为排序的复杂度,O(nlogn)。实现细节还是需要注意的,详见代码。

           关于斜率优化的理论依据,可参见2006年国家集训队汤泽的论文《从一类单调性问题看算法的优化》。简单来说,就是维护一个下凸性的函数。


    // BZOJ 1597
    
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
     typedef long long LL;
     typedef unsigned long long uLL;
     const int N=50000+5;
    
     #define rep(i,a,b) for (int i=a; i<=b; i++)
     #define dep(i,a,b) for (int i=a; i>=b; i--)
     #define read(x) scanf("%d", &x)
     #define uLL unsigned long long 
    
     int Q[2*N], head, tail, n, cnt;
     uLL f[N];
    
     struct Item {
     	int a, b;
     	bool operator < (const Item B) const{
     		return (B.a<a || (B.a==a && (B.b<b)));
     	} 
     }item[N], chosen[N];
    
     double calc(int j, int k) {
     	return (double)(f[k]-f[j])/(chosen[j+1].a-chosen[k+1].a);
     }
    
    int main()
    {
    	read(n);
    	rep(i,1,n) read(item[i].a), read(item[i].b);
    
    	sort(item+1, item+n+1);
    	cnt=0; head=tail=1;
    	while (head<=tail && tail<=n) {	
    		while (head<=tail && item[head].b>=item[tail].b && tail<=n) tail++;
    		chosen[++cnt]=item[head];
    		head=tail;
    	}
     
        head=0; tail=0; f[1]=0;
    	rep(i,1,cnt) {
    		while (head<tail && calc(Q[head], Q[head+1])<chosen[i].b) head++; 
    		int t=Q[head];
    		f[i]=f[t]+(uLL)chosen[i].b*chosen[t+1].a;
    		while (head<tail && calc(Q[tail], i)<calc(Q[tail-1], Q[tail])) tail--;
    		Q[++tail]=i;
    	}
    
    	printf("%llu
    ", f[cnt]);
    
    	return 0;
    }
    


  • 相关阅读:
    使用Modelsim对Nios II系统进行系统级仿真
    电视相关知识学习
    SAD和SATD的区别[摘]
    vim自动补全括号的最好方法
    vim sinppets插件介绍
    在centos下安装开发环境
    初始化以及动态设置Edit控件的背景及字体颜色
    CTreeCtrl SetItemData 释放问题
    C#4.0 新特性 匿名方法,lambds
    对象序列化Serialization与Deserialize方法进行反序列化
  • 原文地址:https://www.cnblogs.com/yearwhk/p/5119886.html
Copyright © 2020-2023  润新知