Welcome to Liukejie'|

liukejie

园龄:1年8个月粉丝:5关注:11

题解:P9789 [ROIR 2020 Day 2] ATM

思路

首先考虑解决前四个子问题,其中 q5。在这种情况下,每个查询都可以独立处理。

在第一个子问题中,b500n500,所以我们可以想到 dp。 用 dpi 表示,如果我们要求的金额为 i,将发行的票据数量。那么 dp0=0,设 m(i) 是不超过 i 的最大面额的纸币,那么 dpi=dpim(i)+1。则答案是 dp1dpb 中的最大值。该方案时间负责度 O(nb)

为了将描述的解决方案衍生到第三个子问题,我们可以在时间负责度中去掉 n。要做到这一点,我们可以发现,m(i) 只随着 i 的增加而增加,所以在计算 dpi 的过程中,我们可以保持 m(i)=aj 的当前值,当我们移动到 i 的下一个值时,检查我们是否不能移动到下一个面额 aj+1 。该方案时间负责度是 O(b+n)

在第二个子任务中,纸币的面额是 2 的连续次方。注意,为了支付一个金额 c,自动取款机的纸票的面额与 c 的二进制符号中的 1 的位置相对应。因此,问题被简化为找到一个不超过 b 的数字,其中包含二进制符号中的最大单位数。假设 b 的二进制符号包含 k 个数字,那么答案中的单位数不超过 k,而数字 2k11 显然小于 b,并且正好包含 k1 个二进制单位。所以答案要么是 b,要么是 2k11,可以得出 O(logb)的解决方案。

第四个子问题的解决已经更接近于完整的解决方案: 这个子问题中对 n,aibi 的约束是最大的,只有 q5 。我们可以使用 O(n) 的时间负责度来这个子问题。

正确的解决方案是基于一个贪心算法。让我们对该问题进行反转。设 mnx 为最小数字 c,使自动取款机为总和 c 发放 x 张纸币。我们仍将以 m(v) 表示不超过 v 的纸币的最大面额。我们可以证明 mnx=mnx1+m(mnx)

一方面,mnx1mnxm(mnx),因为从产生总和 mnx 的纸币集合中,我们总是可以删除最大的纸币(等于 m(mnx)),并得到产生总和 mnxm(mnx)x1 张纸币集合的正确结果。

另一方面,假设我们固定 x,并且 mnx1<mnxm(mnx)。我们可以看到,在这种情况下,数值 mnx1+m(mnx)<mnx,当被要求提供 mnx 时,自动取款机将正好返回纸币—— 一张 m(mn) 纸币(它是最大的,不超过这个数额),以及另外 x1 张纸币,总和等于 mnx1,所以假设不成立,mnx=mnx1+m(mnx)

利用这一观察,我们可以在 O(n) 时间负责度内计算出 mnx 的所有值。

为了解决最后一组问题,我们可以注意到,没有必要对不同的 bj 进行独立求解,但只要通过二分搜索找到最后的 a<bj,并查看相应的 mnx 值的即可。

代码如下

#include <bits/stdc++.h>
#define int long long
using namespace std;
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
const int N = 2e5 + 10;
int n, Q, x, a[N], u[N], v[N];
main()
{
	scanf("%lld", &n);
	rep(i, 1, n) scanf("%lld", &a[i]);
	rep(i, 1, n - 1) 
    {
		int t = (a[i + 1] - u[i] - 1) / a[i];
		u[i + 1] = u[i] + t * a[i];
        v[i + 1] = v[i] + t;
	}
	scanf("%lld", &Q);
    for(; Q; -- Q)
	{
		scanf("%lld", &x);
		int t = upper_bound(a + 1, a + n + 1, x) - a - 1, p = (x - u[t]) / a[t];
		printf("%lld %lld\n", u[t] + p * a[t], p + v[t]);
	}
	return 0;
}
posted @   liukejie  阅读(16)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起