有两种储值方式——无利可图和盈利,“盈利”可以保证盈利,但是有最低储值要求,“无利可图”类型没有利息,但是可以让“盈利”的最低储值降低。在“无利可图”储值$x$元,可以让“盈利”的最低值要求降低$2\times x$元,最低储值不能低于$0$元,两种储值均不能取出。现在Alice
拥有$a$元,并想使存入“盈利”的金额越多越好,求Alice
最多存入多少“盈利”类型的金额。
- $1\leq t\leq 10^4$
- $1\leq a,b\leq 10^9$
假设存入$x$元在“无利可图”,则“盈利”的最低储值降为$b-2\times x$,此时如果Alice
的剩余金额$a-x$可以达到最低储值,则答案为$a-x$,否则为$0$,最小化$x$即可。
1
2
3
4
5
6
7
| void solve() {
ll a, b;
cin >> a >> b;
ll x = max(0ll, b - a);
ll ans = max(0ll, a - x);
cout << ans << '\n';
}
|
柠檬水售卖机有$n$个按钮,但是无法辨认每个按钮对应的槽位还剩多少柠檬水,购买者知道每个槽位最初的柠檬水数量,每按下一个按钮,如果该按钮对应的槽位还有柠檬水,则可以售出$1$瓶柠檬水,若槽位为空,则将没有任何东西掉出来。现在需要精确购买$k$瓶柠檬水,保证柠檬水售卖机里有足够的柠檬水,即$\sum_{i=1}^{n} a_i \ge k$。求问最少的可以保证达成任务的点击次数。
- $1\leq t\leq 10^4$
- $1\leq n\leq 2\times 10^5$
- $1\leq k\leq 10^9$
- $1\leq a_i\leq 10^9$
$n$之和不超过$2\times 10^5$。
基本的贪心购买思路是一直按同一个按钮,直到该槽位为空,考虑到购买$k$瓶至少需要$k$次基本点击,我们需要最小化会浪费的点击次数,即点空槽位的次数。
假设当前每个槽位都至少有$2$瓶柠檬水,我们想最小化点空的次数,最好先把每个按钮都点击$2$次,如果此时的总数足够$k$,我们就计数次数,否则去掉空槽位(通过 1 次点击去除),我们要继续最小化。
二分在每个按钮点击的次数,查询能买到的柠檬水总数,计数最少的次数,即第一个满足总数大于等于$k$的那个次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| ll a[maxn];
void solve() {
ll n, k;
cin >> n >> k;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
auto get_cnt = [&](ll cnt) {
ll ret = 0ll, tot = 0ll;
for (ll i = 1; i <= n; i++) {
if (a[i] < cnt) {
if (tot + a[i] < k) {
ret += a[i] + 1;
tot += a[i];
} else {
ret += k - tot;
break;
}
} else {
if (tot + cnt < k) {
ret += cnt;
tot += cnt;
} else {
ret += k - tot;
break;
}
}
}
return ret;
};
auto get_lemonade = [&](ll cnt) {
ll tot = 0ll;
for (ll i = 1; i <= n; i++) {
if (a[i] < cnt) {
tot += a[i];
} else {
tot += cnt;
}
}
return tot;
};
// 可以获得k的第一个次数的下标
ll l = 1, r = n;
while (l < r) {
ll mid = l + (r - l) / 2;
if (get_lemonade(a[mid]) < k) {
l = mid + 1;
} else {
r = mid;
}
}
ll ans = get_cnt(a[l]);
cout << ans << '\n';
}
|
有$n$个二元组,将这些二元组重新排序后让序列的逆序数最小,输出排序后的结果。
- $1\leq t\leq 10^4$
- $1\leq n\leq 10^5$
- $1\leq a_{i,j}\leq 10^9$
排序,将二元组按照两数之和、第一位数、第二位数的优先级顺序从小到大排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| void solve() {
ll n;
cin >> n;
vector<pll> v;
for (ll i = 1; i <= n; i++) {
pll p;
cin >> p.first >> p.second;
v.push_back(p);
}
sort(v.begin(), v.end(), [&](pll p1, pll p2) {
return make_tuple(p1.first + p1.second, p1.first, p1.second) <
make_tuple(p2.first + p2.second, p2.first, p2.second);
});
for (auto i : v) {
cout << i.first << ' ' << i.second << ' ';
}
cout << '\n';
}
|
问答系统,对第$i$个题,如果选择回答,可以获得$a_i$分数,接下来只能选择序号$j\lt i$且没有操作过的问题。也可以选择跳过,接下来可以选择$j\leq b_i$的未操作过的题目。从$1$号问题开始回答。
注意到,当跳到索引$i$时,所有$1\leq k\leq i$的没有回答过的题目,都可以通过依次回答。我们只需要贪心的选择前置和-到达某个位置的最小代价即可。
考虑用一个全局$multiset$维护当前可以使用的代价,从$1$号问题开始,每个问题的$b_i$指向一个到$b_i+1$会“失效”的最小代价,每次移动时去除失效代价,并加入新的最小代价,维护最大得分即可。
维护时可以注意到$b_i\leq i$的跳题没有意义,不如直接向前答题,可以通过判断去除这种移动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| void solve() {
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
for (ll i = 1; i <= n; i++) {
cin >> b[i];
}
// ll ans = a[1];
multiset<ll> st; // 可行代价
map<ll, vector<ll>> mp; // 存储到达i的所有可行代价
mp[2].push_back(0);
st.insert(0);
ll ans = a[1];
for (ll i = 1; i <= n; i++) {
for (auto x : mp[i]) {
st.erase(st.find(x)); // 删去不再适用的代价
}
if (st.empty()) {
dis[i] = -1; // 没有可用的代价
} else {
dis[i] = *st.begin();
}
if (dis[i] == -1)
continue;
ans = max(ans, pre[i] - dis[i]);
if (b[i] <= i)
continue;
// 可以拓展到点b[i],b[i]+1时刻这些代价都不再适用
ll d = dis[i] + a[i];
mp[b[i] + 1].push_back(d);
st.insert(d);
}
cout << ans << '\n';
}
|