BZOJ-1492-货币兑换cash-NOI2007-CDQ分治

描述




分析

  • CDQ分治的例题, 具体怎么分析看当时的课件.
  • DP优化, 维护凸线, 斜率递增.
  • 说白了就是一个分治
  • memcpy 不比 for 循环快
  • 函数参数加引用比不加引用还慢了
  • 自己推了一遍分析过程, 放代码后面了

代码

#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
using namespace std;

const int maxn = 100000 + 10;
const double INF = 1e20;
const double eps = 1e-9;

struct Node {
	int id;
	double a, b, r, x, y, k;

	void read(int i) {
		id = i;
		scanf("%lf %lf %lf", &a, &b, &r);
		k = -a/b;
	}

	bool operator < (const Node& rhs) const {
		if(x < rhs.x) return 1;
		return fabs(x-rhs.x) < eps && y < rhs.y;
	}

} A[maxn], T[maxn];

bool cmpK(const Node& lhs, const Node& rhs) {
	return lhs.k > rhs.k;
}

double getK(Node i, Node j) {
	if(fabs(j.x-i.x) < eps) return INF;
	return (j.y-i.y) / (j.x-i.x);
}

int S[maxn];
double f[maxn];

void solve(int L, int R)
{
	if(L == R) {
		f[L] = max(f[L], f[L-1]);
		A[L].y = f[L] / (A[L].a * A[L].r + A[L].b);
		A[L].x = A[L].y * A[L].r;
		return;
	}
	int M = (L+R) >> 1;
	int p = L, q = M+1;
	for(int i = L; i <= R; i++) {
		Node& x = A[i];
		if(x.id <= M) T[p++] = A[i];
		else T[q++] = A[i];
	}
	for(int i = L; i <= R; i++) A[i] = T[i];
	solve(L, M);
	int top = 0;
	for(int i = L; i <= M; i++) {
		while(top > 1 && getK(A[S[top-2]], A[S[top-1]]) - getK(A[S[top-2]], A[i]) < eps) top--;
		S[top++] = i;
	}
	for(int i = M+1, j = 0; i <= R; i++) {
		while(j < top-1 && A[i].k - getK(A[S[j]], A[S[j+1]]) < eps) j++;
		Node &x = A[i], &y = A[S[j]];
		f[x.id] = max(f[x.id], y.x*x.a + y.y*x.b);
	}
	solve(M+1, R);
	p = L;
	q = M+1;
	for(int i = L; i <= R; i++)
		if(p <= M && (A[p] < A[q] || q > R)) T[i] = A[p++];
		else T[i] = A[q++];
	for(int i = L; i <= R; i++) A[i] = T[i];
}

int main()
{
	int n;
	scanf("%d %lf", &n, &f[0]);
	for(int i = 1; i <= n; i++) A[i].read(i);
	sort(A+1, A+n+1, cmpK);
	solve(1, n);
	printf("%.3lf\n", f[n]);
}
// S的现金在Rate = r时 可以买A(B)金券a(b)单位.
a*A + b*B = S
a/b = r => b = a/r
a*A + a/r*B = S
a(A + B/r) = S
a = S*r / (A*r + B)

// 第 i 天最多获得的A金券的数量
f[i] = max{f[j] * A[j] + f[j]/r[j] * B[j], j < i} * r/(A[i]*r[i] + B[i])

j better than k :
    f[j] * A[i] + f[j]/r[j] * B[i] > f[k] * A[i] + f[k]/r[k] * B[i]
    f[j] * A[i] + f[j]/r[k] * B[i] - f[k] * A[i] - f[k]/r[k] * B[i] > 0
    (f[j]-f[k])*A[i] + (f[j]/r[j] - f[k]/r[k])*B[i] > 0
    
    // 发现因不知道 f[j]和f[k] 的大小关系需要分类讨论了, 不放设 f[j] < f[k]
    // 设 g[x] = f[x] / r[x], g[x]的意义也很清楚, 就是第 i 天最多获得的B金券的数量
    
    (f[j]-f[k])*A[i] + (g[j]-g[k])*B[i] > 0
    (g[j]-g[k]) / (f[j]-f[k]) < -A[i]/B[i]
    // 明显带有斜率的意味了, 把 f[] 看作 x, g[] 看作 y
    // 那么可以看出在更新 i 的答案时, 把 i 之前的元素按 x(f[]) 从小到大排序
    // 如果 j 的后面存在一个 k 使得直线 jk 的斜率不小于 -A[i]/B[i]
    // 就说明 j 不优于 k
    // 为判断 j 是否比后面所有点更优, 可以保存 j 之后使 jk 斜率最大的 k, 通过比较斜率判断是否可以用 j 更新 i
    // 上面说的对于每个 j 都要保存一个使 jk 斜率最大的 k, 那么最后存下来点就组成一个凸线
    
    // 凸线上从第一个点开始斜率递减, 所以更新答案要使被更新的区域 -A[i]/B[i] 递减, 因此最开始要按 -A[i]/B[i] 从大到小排序
    // 来保证每一部分的 -A[i]/B[i] 都是有序的

      
      
     
     
    
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值