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

题目传送门

纪念一下第一道(?)自己 yy 出来的 NOI 题。
考虑 dp,\(dp[i]\) 表示到第 \(i\) 天最多有多少钱。
那么有 \(dp[i]=\max\{\max\limits_{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 分治中递归的顺序不能搞错,必须按照递归左半边 \(\rightarrow\) 用左半边更新右半边答案 \(\rightarrow\) 递归右半边的顺序进行,否则无法从 \(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\n",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]))
*/
posted @ 2020-07-18 23:26  tzc_wk  阅读(119)  评论(1编辑  收藏  举报