zoukankan      html  css  js  c++  java
  • BZOJ1492: [NOI2007]货币兑换Cash 【dp + CDQ分治】

    1492: [NOI2007]货币兑换Cash

    Time Limit: 5 Sec  Memory Limit: 64 MB
    Submit: 5391  Solved: 2181
    [Submit][Status][Discuss]

    Description

    小Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下
    简称B券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。每天随着市场的起伏波动,
    两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A券 和 B券 的
    价值分别为 AK 和 BK(元/单位金券)。为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法
    。比例交易法分为两个方面:(a)卖出金券:顾客提供一个 [0,100] 内的实数 OP 作为卖出比例,其意义为:将
     OP% 的 A券和 OP% 的 B券 以当时的价值兑换为人民币;(b)买入金券:顾客支付 IP 元人民币,交易所将会兑
    换给用户总价值为 IP 的金券,并且,满足提供给顾客的A券和B券的比例在第 K 天恰好为 RateK;例如,假定接
    下来 3 天内的 Ak、Bk、RateK 的变化分别为:
    假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
    注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经
    知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能
    够获得多少元钱。

    Input

    输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
    K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
    0^9。
    【提示】
    1.输入文件可能很大,请采用快速的读入方式。
    2.必然存在一种最优的买卖方案满足:
    每次买进操作使用完所有的人民币;
    每次卖出操作卖出所有的金券。

    Output

    只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。

    Sample Input

    3 100
    1 1 1
    1 2 2
    2 2 3

    Sample Output

    225.000

    HINT




    题解

    蒟蒻经济头脑真的差。。
    按照生活经验要使利益最大每次一定要全部换完
    我们设f[i]表示第i天最大人民币数
    则有f[i] = max{A[i] * R[j] * f[j] / (R[j] * A[j] + B[j]) + B[i] * f[j] / (R[j] * A[j] + B[j])}
    我们就有了O(n ^ 2)算法
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define fo(i,x,y) for (int i = (x); i <= (y); i++)
    #define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
    using namespace std;
    const int maxn = 100005,maxm = 100005,INF = 1000000000;
    inline int read(){
    	int out = 0,flag = 1;char c = getchar();
    	while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
    	while (c >= 48 && c <= 57) {out = out * 10 + c - 48; c = getchar();}
    	return out * flag;
    }
    int N;
    double S,A[maxn],B[maxn],R[maxn],f[maxn],ans;
    int main()
    {
    	N = read(); S = read();
    	REP(i,N) A[i] = read(),B[i] = read(),R[i] = read();
    	ans = S;
    	f[1] = S / (R[1] * A[1] + B[1]);
    	for (int i = 2; i <= N; i++){
    		for (int j = 1; j < i; j++)
    			ans = max(ans,f[j] * B[i] + R[j] * f[j] * A[i]);
    		f[i] = ans / (R[i] * A[i] + B[i]);
    	}
    	printf("%.3lf
    ",f[N] * B[N] + R[N] * f[N] * A[N]);
    	return 0;
    }
    


    然而满分算法是很艰难的,需要我们优化
    同样的,若k < j < i 且j优于k,就有
    A[i] * R[j] * f[j] / (R[j] * A[j] + B[j]) + B[i] * f[j] / (R[j] * A[j] + B[j]) >  A[i] * R[k] * f[k] / (R[k] * A[k] + B[k]) + B[i] * f[k] / (R[k] * A[k] + B[k])
    令yj = f[j] / (R[j] * A[j] + B[j])
    令xj = R[j] * yj
    化为(yj - yk) / (xj - xk) > -A[i] / B[i]
    就可以把每一天看做一个点(xi,yi)了
    我们对于f[i],有斜率k = -A[i] / B[i]的直线
    我们需要找到i之前所有点组成的凸包中过该直线y轴截距最大的点
    如果所有点x单调且斜率单调的话就可以用单调队列维护了
    很可惜这题二者都不单调
    所以我们要用splay维护凸包【好难写,而且也不会】

    没办法听说有一种东西叫CDQ分治
    主要思想是这样的,先将各点按斜率排序【每个点对应的斜率与f无关,可以直接得到】
    solve(L,R)可以算出[L,R]内所有的f值且算完后所有点按坐标排序
    那么我们可以先算solve(L,mid),之后对[L,mid]维护一个凸包用来更新[mid + 1,R]中的f值
    由于左区间坐标有序,而右区间斜率有序,就可以用一个栈直接维护了
    之后solve(mid + 1,R)
    算完后再将所有点按坐标排序【类似于归并的合并】

    好难写QAQ,蒟蒻写了一个下午

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define eps 1e-9
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define fo(i,x,y) for (int i = (x); i <= (y); i++)
    #define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
    using namespace std;
    const int maxn = 100005,maxm = 100005,INF = 1000000000;
    int n,top,st[maxn];
    double f[maxn];
    struct node{double x,y,a,b,r,k;int id;}p[maxn],t[maxn];
    inline bool operator < (const node& a,const node& b){return a.k > b.k;}
    inline double slope(int u,int v){
    	if (!v) return -1e20;
    	if (fabs(p[u].x - p[v].x) < eps) return 1e20;
    	return (p[u].y - p[v].y) / (p[u].x - p[v].x);
    }
    void solve(int L,int R){
    	if (L == R){
    		f[L] = max(f[L],f[L - 1]);
    		p[L].y = f[L] / (p[L].a * p[L].r + p[L].b);
    		p[L].x = p[L].r * p[L].y;
    		return;
    	}
    	int mid = (L + R) >> 1,l1 = L,l2 = mid + 1,j = 1;
    	for (int i = L; i <= R; i++)
    		if (p[i].id <= mid) t[l1++] = p[i];
    		else t[l2++] = p[i];
    	for (int i = L; i <= R; i++) p[i] = t[i];
    	solve(L,mid);
    	top = 0;
    	for (int i = L; i <= mid; i++){
    		while (top > 1 && slope(st[top - 1],st[top]) < slope(st[top - 1],i) + eps) top--;
    		st[++top] = i;
    	}
    	st[++top] = 0;
    	for (int i = mid + 1; i <= R; i++){
    		while (j < top && slope(st[j],st[j + 1]) + eps > p[i].k) j++;
    		f[p[i].id] = max(f[p[i].id],p[st[j]].x * p[i].a + p[st[j]].y * p[i].b);
    	}
    	solve(mid + 1,R);
    	l1 = L; l2 = mid + 1;
    	for (int i = L; i <= R; i++){
    		if (((p[l1].x < p[l2].x || (fabs(p[l1].x - p[l2].x) < eps && p[l1].y < p[l2].y)) || l2 > R) && l1 <= mid) t[i] = p[l1++];
    		else t[i] = p[l2++];
    	}
    	for (int i = L; i <= R; i++) p[i] = t[i];
    }
    int main()
    {
    	scanf("%d%lf",&n,&f[0]);
    	REP(i,n){
    		scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].r);
    		p[i].k = -p[i].a / p[i].b; p[i].id = i;
    	}
    	sort(p + 1,p + 1 + n);
    	solve(1,n);
    	printf("%.3lf
    ",f[n]);
    	return 0;
    }
    


     
  • 相关阅读:
    查看mysql版本的四种方法及常用命令
    newInstance和new的区别(good)
    Citrix 服务器虚拟化之六 Xenserver虚拟机创建与快照
    Java实现 蓝桥杯 算法训练 排序
    Java实现 蓝桥杯 算法训练 排序
    Java实现 蓝桥杯 算法训练 排序
    Java实现 蓝桥杯 算法训练 2的次幂表示
    Java实现 蓝桥杯 算法训练 2的次幂表示
    Java实现 蓝桥杯 算法训练 前缀表达式
    Java实现 蓝桥杯 算法训练 前缀表达式
  • 原文地址:https://www.cnblogs.com/Mychael/p/8282821.html
Copyright © 2011-2022 走看看