Loading

P4027 [NOI2007] 货币兑换 题解

P4027 [NOI2007] 货币兑换

题意

你有一些钱,有两种金券(A, B)。

\(i\) 天,你可以如下操作:

  • 按照当天价格卖出 A, B 金券各 \(k%\)

  • 按照当天价格花 \(k\) 元买金券,A, B 分别买到 \(p, q\) 个,满足 \(\frac p q = \text{Rate}_i\)

给定 \(A_{1,\cdots,n}, B_{1,\cdots,n}, \text{Rate}_{1,\cdots,n}\),分别为每天 A, B 金券价格和购买比例。

求第 \(n\) 天最大收益。

\(n \le 10 ^ 5, A_{1,\cdots,n}, B_{1,\cdots,n} \le 10, \text{Rate}_{1,\cdots,n} \le 100\)

思路

DP

题目提示我们每次卖都是卖完金券,买都花光钱。所以我们是交替的一次买,一次卖。

对于分界线两边:

  • 左侧最后一个是买,右侧第一个是卖,需要知道左侧买了多少金券(两种,分别多少)。
    又因为买的时候花光钱,我们只需要知道左侧最后一个买的位置最多有多少钱即可。

  • 左侧最后一个是卖,右侧第一个是买,需要知道左侧还有多少钱。

\(f_i\) 表示第 \(i\) 天最多拥有的钱(这一天不买金券),\(p_i, q_i\) 分别表示拿 \(f_i\) 的钱在第 \(i\) 天能买多少 A / B 金券。
下记 \(R_i\)\(\text{Rate}_i\)

\[\begin{cases} A_i \times p_i + B_i \times q_i = f_i \\ \frac {p_i} {q_i} = R_i \end{cases} \]

解得:

\[\begin{cases} p_i = f_i \times \frac {R_i} {A_i \times R_i + B_i} \\ q_i = f_i \times \frac {1} {A_i \times R_i + B_i} \end{cases} \]

考虑转移,我们枚举 \(i\),枚举 \(j\),算出在 \(j\) 花光钱买金券,在 \(i\) 全部卖掉的钱。

\[f_i = \max \left ( f_{i - 1}, \max \limits _ {j \lt i} \left ( A_i \times p_j + B_i \times q_j \right ) \right ) \]

斜率优化

原式子 \(f_i = A_i \times p_j + B_i \times q_j\) 中含有两个同时含有 \(i, j\) 的项。
但是我们考虑在两侧除一个 \(B_i\),得到 \(\frac {f_i} {B_i} = \frac {A_i} {B_i} \times p_j + q_j\)

\[\underline{q_j}_y = \underline{- \frac {A_i} {B_i}} _ k \times \underline{p_j}_x + \underline{\frac {f_i} {B_i}}_b \]

分析插入顺序和查询斜率顺序,发现两个顺序都是乱的,所以需要动态维护凸包。

Splay

我们用 Splay 维护凸包。

对于凸包上的每个点,维护其左侧直线斜率 \(lk\) 及右侧直线斜率 \(rk\)

查询最优决策时,从根节点往下走,找到 \(lk_j \le - \frac {A_i} {B_i} \le rk_j\)\(j\)

插入新的节点时,先找到左侧最靠右的凸包上的点(这里是指加入新点后还在凸包上)和右侧最靠左的凸包上的点,以此计算新点的 \(lk, rk\)
若新点并不会出现在凸包上,即 \(lk \lt rk\),需要删除。

代码

const int N = 1e5 + 5;
const double eps = 1e-9;
const double inf = 1e9;

int n;
double dp[N];
double a[N], b[N], r[N];
double p[N], q[N];

struct node{
	int fa, son[2];
	double lk, rk;
} t[N];
int tot, rt;

#define fa(x) t[x].fa
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]

bool which_son(int x){
	return x == rs(fa(x));
}
void rotate(int x){
    int Fa = fa(x), GFa = fa(Fa);
    int flg_x = which_son(x);
    t[Fa].son[flg_x] = t[x].son[flg_x ^ 1];
    t[x].son[flg_x ^ 1] = Fa;
    if(GFa) t[GFa].son[which_son(Fa)] = x;
    if(t[Fa].son[flg_x]) fa(t[Fa].son[flg_x]) = Fa;
    fa(Fa) = x;
    fa(x) = GFa;
}
void splay(int &root, int x){
    int fa_root = fa(root);
	for(int Fa = fa(x); (Fa = fa(x)) != fa_root; rotate(x)){
		if(fa(Fa) != fa_root){
			if(which_son(x) == which_son(Fa)){
				rotate(Fa);
			}else{
				rotate(x);
			}
		}
	}
	root = x;
}
int find_dec(double qk){
	int x = rt;
	while(x){
		if(t[x].lk >= qk && t[x].rk <= qk){
			break;
		}
		if(t[x].lk < qk){
			x = ls(x);
		}else{
			x = rs(x);
		}
	}
	return x;
} 
double calc_k(int i, int j){
	double x_1 = p[i], y_1 = q[i];
	double x_2 = p[j], y_2 = q[j];
	if(fabs(x_1 - x_2) < eps){
		return (y_1 < y_2) ? inf : -inf;
	}
	return (y_2 - y_1) / (x_2 - x_1);
}
int query_pre(int x){
	int y = ls(x), res = y;
	while(y){
		if(t[y].lk >= calc_k(y, x)){
			res = y;
			y = rs(y);
		}else{
			y = ls(y);
		}
	}
	return res;
}
int query_nxt(int x){
	int y = rs(x), res = y;
	while(y){
		if(t[y].rk <= calc_k(x, y)){
			res = y;
			y = ls(y);
		}else{
			y = rs(y);
		}
	}
	return res;
}
void insert(int i){ // insert point (p_i, q_i)
	int x = rt, Fa = 0;
	while(x){
		if(p[x] >= p[i]){
			Fa = x;
			x = ls(x);
		}else{
			Fa = x;
			x = rs(x);
		}
	}
	t[Fa].son[p[i] > p[Fa]] = i;
	fa(i) = Fa;
	splay(rt, i);
}
void init(int x){
	if(ls(x)){
		int pre = query_pre(x);
		splay(ls(rt), pre);
		rs(pre) = 0;
		t[pre].rk = t[x].lk = calc_k(pre, x);
	}else{
		t[x].lk = inf;
	}
	if(rs(x)){
		int nxt = query_nxt(x);
		splay(rs(rt), nxt);
		ls(nxt) = 0;
		t[nxt].lk = t[x].rk = calc_k(x, nxt);
	}else{
		t[x].rk = -inf;
	}
	if(t[x].lk <= t[x].rk + eps){
		rt = ls(x), fa(rt) = 0;
		rs(rt) = rs(x), fa(rs(rt)) = rt;
		t[rt].rk = t[rs(rt)].lk = calc_k(rt, rs(rt));
	}
}

#undef fa
#undef ls
#undef rs

void solve_test_case(){
	cin >> n >> dp[0];
	rep(i, 1, n){
		cin >> a[i] >> b[i] >> r[i];
		int j = find_dec(-a[i] / b[i]);
		dp[i] = max(dp[i - 1], a[i] * p[j] + b[i] * q[j]);
		p[i] = dp[i] * r[i] / (a[i] * r[i] + b[i]);
		q[i] = dp[i] / (a[i] * r[i] + b[i]);
		insert(i);
		init(i);
	}
	cout << fixed << setprecision(3) << dp[n];
}
posted @ 2026-01-07 14:52  lajishift  阅读(2)  评论(0)    收藏  举报