題目鏈接:[NOI2007]貨幣兌換Cash
分析請見CDQ論文: 從《Cash》談一類分治算法的應用
代碼如下:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=200000+5;
const double inf= 999999999999.00, eps= 1e-9;
int n,S;
int Q[maxn],rear;
double f[maxn];
struct node{
int id;
double x,y,a,b,k,rate;
// x: 第一種商品的數量 y: 第二種商品的數量.
bool operator < (const node p) const { // 按斜率 k= - a[i]/b[i] 降序排序 .
return k > p.k;
}
void put(){
cout<<x<<" "<<y<<endl;
}
}s[maxn],temp[maxn];
double slope(int A,int B){
if(B==0) return -inf;
if( fabs(s[A].x-s[B].x)< eps ) return inf;
return (s[A].y-s[B].y) / (s[A].x-s[B].x);
}
void Solve(int L,int R){
if(L==R){
f[L]= max(f[L],f[L-1]);
s[L].y= f[L]/(s[L].rate*s[L].a+s[L].b);
s[L].x= s[L].y*s[L].rate;
return ;
}
int mid= (L+R)>>1;
int i=L,j=mid+1,p;
// 按照原序排序,保證左邊能更新右邊,而且左右區間中 k= -a[i]/b[i] 也是分別遞減的.
for(p=L;p<=R;p++){
if(s[p].id<=mid) temp[i++]= s[p];
else temp[j++]= s[p];
}
for(i=L;i<=R;i++)s[i]= temp[i];
Solve(L,mid);
//維護左邊的凸包,左區間已經按照x排好序.
rear=0;
for(i=L; i<=mid; i++){
while(rear>1 && slope(Q[rear],Q[rear-1])< slope(Q[rear-1],i)+eps) rear--;
Q[++rear]= i;
}
Q[++rear]=0;
// 用左區間更新右區間
for(i=mid+1, j=1 ;i<=R;i++){
while(j<rear && slope(Q[j],Q[j+1])+eps > s[i].k) j++;
//cout<< Q[j]<<" -> "<<s[i].id<<" "<<Q[j+1]<<" "<<s[i].k<<endl;
//s[Q[j]].put();
f[s[i].id]= max(f[s[i].id], s[Q[j]].x*s[i].a+s[Q[j]].y*s[i].b);
}
Solve(mid+1,R);
//按照 x值大小歸併排序
i=L; j=mid+1;
for(p=L;p<=R && i<=mid && j<=R;p++){
if(s[i].x<s[j].x || (fabs(s[i].x-s[j].x)<eps && s[i].y<s[j].y)) temp[p]= s[i++];
else temp[p]= s[j++];
}
while(i<=mid) temp[p++]= s[i++];
while(j<=R) temp[p++]= s[j++];
for(i=L;i<=R;i++) s[i]=temp[i];
}
int main(){
int i,j;
scanf("%d%lf",&n,&f[0]);
for(i=1;i<=n;i++){
scanf("%lf%lf%lf",&s[i].a,&s[i].b,&s[i].rate);
s[i].id=i; s[i].k= -s[i].a/s[i].b;
}
sort(s+1,s+1+n);
Solve(1,n);
//for(i=1;i<=n;i++)
printf("%.3lf\n",f[n]);
return 0;
}