• LOJ 2353 & 洛谷 P4027 [NOI2007]货币兑换(CDQ 分治维护斜率优化)


    题目传送门

    纪念一下第一道(?)自己 yy 出来的 NOI 题。
    考虑 dp,(dp[i]) 表示到第 (i) 天最多有多少钱。
    那么有 (dp[i]=max{maxlimits_{j=1}^{i-1}a[i]*(dp[j]/(a[j]*r[j]+b[j])*r[j])+b[i]*dp[j]/(a[j]*r[j]+b[j]),dp[i-1]})
    我们稍微观察一下,里面那个式子似乎能写成斜率优化的样子:
    (t[j]=dp[j]/(a[j]*r[j]+b[j])),假设有 (j>k) 并且从 (j) 转移比从 (k) 转移更优,那么:
    (a[i]*t[j]*r[j]+b[i]*t[j]>a[i]*t[k]*r[k]+b[i]*t[k])
    将这个式子变个形,可以得到:
    (frac{t[j]*r[j]-t[k]*r[k]}{-t[j]+t[k]}>frac{b[i]}{a[i]})
    但显然 (-t[j])(横坐标) 是不单调的,(frac{b[i]}{a[i]})(斜率)也是不单调的。
    解决斜率不单调的问题,这与任务安排有点类似,把所有点扔进凸包后二分斜率找到最优决策点。
    解决横坐标不单调的问题,可以借鉴Cow School的方法,使用CDQ分治将前一半的点按横坐标排序后建立凸包。
    具体实现时还有诸多细节需考虑,例如 CDQ 分治中递归的顺序不能搞错,必须按照递归左半边 ( ightarrow) 用左半边更新右半边答案 ( ightarrow) 递归右半边的顺序进行,否则无法从 (dp[i]) 更新到 (dp[i+1])。还有就是特判斜率不存在的情况。

    /*
    Contest: -
    Problem: P4027
    Author: tzc_wk
    Time: 2020.7.18
    */
    #include <bits/stdc++.h>
    using namespace std;
    #define fi			first
    #define se			second
    #define fz(i,a,b)	for(int i=a;i<=b;i++)
    #define fd(i,a,b)	for(int i=a;i>=b;i--)
    #define foreach(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
    #define all(a)		a.begin(),a.end()
    #define fill0(a)	memset(a,0,sizeof(a))
    #define fill1(a)	memset(a,-1,sizeof(a))
    #define fillbig(a)	memset(a,0x3f,sizeof(a))
    #define fillsmall(a) memset(a,0xcf,sizeof(a))
    #define y1			y1010101010101
    #define y0			y0101010101010
    typedef pair<int,int> pii;
    inline int read(){
    	int x=0,neg=1;char c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')	neg=-1;
    		c=getchar();
    	}
    	while(isdigit(c))	x=x*10+c-'0',c=getchar();
    	return x*neg;
    }
    const double EPS=1e-10;
    int n=read();
    struct data{
    	double a,b,r,dp;
    } a[100005],tmp[100005];
    inline double _t(int j){
    	return 1.0*a[j].dp/(a[j].a*a[j].r+a[j].b);
    }
    inline double sl(int j,int k){
    	if(abs((-_t(j))-(-_t(k)))<EPS)	return 1e9;
    	return 1.0*(_t(j)*a[j].r-_t(k)*a[k].r)/((-_t(j))-(-_t(k)));
    }
    inline void merge(int l,int r,int mid){
    	int p1=l,p2=mid+1;
    	for(int i=l;i<=r;i++){
    		if(p1<=mid&&(p2>r||_t(p1)>_t(p2)))
    			tmp[i]=a[p1++];
    		else
    			tmp[i]=a[p2++];
    	}
    	for(int i=l;i<=r;i++){
    		a[i]=tmp[i];
    	}
    }
    int q[100005];
    inline int bsearch(int l,int r,double slo){
    	if(l==r)	return q[l];
    	int mid=(l+r)>>1;
    	if(sl(q[mid+1],q[mid])-slo>EPS)	return bsearch(mid+1,r,slo);
    	else							return bsearch(l,mid,slo);
    }
    inline void CDQ(int l,int r){
    	if(l==r){
    		a[l+1].dp=max(a[l+1].dp,a[l].dp);
    		return;
    	}
    	int mid=(l+r)>>1;
    	CDQ(l,mid);
    	int hd=1,tl=0;
    	fz(i,l,mid){
    		while(hd<tl&&sl(i,q[tl])-sl(q[tl],q[tl-1])>EPS)	tl--;
    		q[++tl]=i;
    	}
    	fz(i,mid+1,r){
    		int j=bsearch(hd,tl,1.0*a[i].b/a[i].a);
    		a[i].dp=max(a[i].dp,a[i].a*(a[j].dp/(a[j].a*a[j].r+a[j].b)*a[j].r)+a[i].b*a[j].dp/(a[j].a*a[j].r+a[j].b));
    		a[i+1].dp=max(a[i+1].dp,a[i].dp);
    	}
    	CDQ(mid+1,r);
    	merge(l,r,mid);
    }
    signed main(){
    	cin>>a[1].dp;
    	fz(i,1,n)	scanf("%lf %lf %lf",&a[i].a,&a[i].b,&a[i].r);
    	CDQ(1,n);
    	printf("%.3lf
    ",a[n+1].dp);
    	return 0;
    }
    /*
    dp[i]=max{a[i]*(dp[j]/(a[j]*r[j]+b[j])*r[j])+b[i]*dp[j]/(a[j]*r[j]+b[j])}
    
    Let t[j] be dp[j]/(a[j]*r[j]+b[j])
    
    a[i]*t[j]*r[j]+b[i]*t[j]
    
    j>k
    
    a[i]*t[j]*r[j]+b[i]*t[j]>a[i]*t[k]*r[k]+b[i]*t[k]
    a[i]*(t[j]*r[j]-t[k]*r[k])>b[i]*(t[k]-t[j])
    (t[j]*r[j]-t[k]*r[k])
    --------------------->b[i]/a[i]
       (-t[j]-(-t[k]))
    */
    
  • 相关阅读:
    Java 7如何操纵文件属性
    MS Server中varchar与nvarchar的区别
    【Unity3D】【NGUI】UICamera
    2007LA 3902 网络(树+贪心)
    读取图片的几种方式
    AssetsLibrary 实现访问相册,选取多张照片显示
    UIImagePickerController的用法
    画板的实现
    最近的状态
    富文本的使用-----实现图文混排 文字的检索 (正则表达式)
  • 原文地址:https://www.cnblogs.com/ET2006/p/13338122.html
Copyright © 2020-2023  润新知