problem
【题目描述】
你在 NOIP 2018 的赛场上遇到了「货币系统」一题。你没有写出这题,导致网友的国度简化货币系统的任务失败了。网友的国度的货币系统现在十分混乱。
网友的国度现今有两套货币系统「忘忧」和「网游」。为了方便使用,它们有一个共用的货币单位「𰻞」,而且二者都可以凑出价值任意整数𰻞的货币。
你现在希望用人民币来兑换一些网友的国度的货币。你需要兑换多次,每次恰好兑换 1 𰻞。
忘忧和网游的汇率互相独立,而且会动态改变。具体而言:
- 第一次兑换忘忧,1 𰻞花费 a 元;由于网友的国度持有的忘忧变少了,因此其价格
上涨:每兑换 1 𰻞,下一𰻞的价格涨价 b 元;
形式化地,第 i 次兑换忘忧的价格是一𰻞 b(i −1) + a 元。 - 第一次兑换网游,1 𰻞花费 c 元;由于网友的国度持有的网游变少了,因此其价格上涨:每兑换 1 𰻞,下一𰻞的价格涨价 d 元;形式化地,第 i 次兑换网游的价格是一𰻞 d(i −1) + c 元。
为了能够随机应变,你希望提前知道给出的 q 种 a, b, c, d 下,用 x 元钱分别最多能兑换多少𰻞。
每次询问相互独立
【输入格式】
第一行一个正整数 q,表示有 q 个询问。
接下来 q 行,每行 5 个正整数 a, b, c, d, x,分别表示忘忧、网游的汇率变化方式和你所拥有的钱。
【输出格式】
输出共 qqq 行,分别表示每次询问中你最多能兑换到多少𰻞。
1≤a,b,c,d,x≤1018,1≤q≤1051 ≤a, b, c, d, x ≤10^{18},1 ≤q ≤10^51≤a,b,c,d,x≤1018,1≤q≤105
solution
看到这种两个函数都是单增的,且最后总限制为 xxx。
我就想到了 CSP-S 2021 T1
,当时我就是采取的三分骗到了不错的分数。
这道题看起来似乎与二分/三分很有关联。
但是这两个函数不能拼成一个单调或有峰值的函数,能够直接做。
很妙的是,直接二分花费最大值 midmidmid。
让两种物品的单个兑换最大花费不超过 midmidmid。
先计算出每种物品最多能买多少个,最大的那个花费不超过 midmidmid,然后可以等差数列 O(1)O(1)O(1) 计算出。
具体而言,假设最多能买 iii 个,即 b(i−1)+a≤midb(i-1)+a\le midb(i−1)+a≤mid。
花费则为 ∑k=1ib(k−1)+a=∑k=1ibk+(a−b)∗i=(b+b∗i)i2+i∗(a−b)\sum_{k=1}^ib(k-1)+a=\sum_{k=1}^ibk+(a-b)*i=\frac{(b+b*i)i}{2}+i*(a-b)∑k=1ib(k−1)+a=∑k=1ibk+(a−b)∗i=2(b+b∗i)i+i∗(a−b)。
再把两种物品的花费求和,与 xxx 限制比较。
当然可能会剩下一点 rrr,也许还可以再买两种物品中的一个,特判一下。
最开始求最大花费不超过 midmidmid 的物品个数也请注意写法。——来自伞兵博主。
code
#include <bits/stdc++.h>
using namespace std;
#define int __int128
int q, x, a, b, c, d, ans;void read( int &x ) {x = 0; char s = getchar();while( s < '0' or s > '9' ) s = getchar();while( '0' <= s and s <= '9' ) x = ( x << 1 ) + ( x << 3 ) + ( s - '0' ), s = getchar();
}void print( int x ) {if( x > 9 ) print( x / 10 );putchar( x % 10 + '0' );
}bool check( int v ) {int i = max( (int)0, ( v - a ) / b + 1 );int j = max( (int)0, ( v - c ) / d + 1 );if( i and b * ( i - 1 ) + a > v ) i --;if( j and d * ( j - 1 ) + c > v ) j --;int cost = ( i + 1 ) * b * i / 2 + i * ( a - b ) + ( j + 1 ) * d * j / 2 + j * ( c - d );if( cost > x ) return 0;int r = x - cost, tot = i + j;if( r >= b * i + a ) r -= b * i + a, tot ++, i ++;if( r >= d * j + c ) r -= d * j + c, tot ++, j ++;ans = max( ans, tot );return 1;
}signed main() {freopen( "money.in", "r", stdin );freopen( "money.out", "w", stdout );read( q );while( q -- ) {read( a ), read( b ), read( c ), read( d ), read( x );int l = 0, r = x; ans = 0;while( l <= r ) {int mid = ( l + r ) / 2;if( check( mid ) ) l = mid + 1;else r = mid - 1;}print( ans );puts("");}return 0;
}