Dashboard - Codeforces Round 980 (Div. 2) - Codeforces
火车头
#define _CRT_SECURE_NO_WARNINGS 1
#include <algorithm>
#include <array>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#define ft first
#define sd second
#define yes cout << "yes\n"
#define no cout << "no\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define sort_all(x) sort(all(x))
#define sort1_all(x) sort(all1(x))
#define reverse_all(x) reverse(all(x))
#define reverse1_all(x) reverse(all1(x))
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#define RED cout << "\033[91m"
#define GREEN cout << "\033[92m"
#define YELLOW cout << "\033[93m"
#define BLUE cout << "\033[94m"
#define MAGENTA cout << "\033[95m"
#define CYAN cout << "\033[96m"
#define RESET cout << "\033[0m"
// 红色
#define DEBUG1(x) \
RED; \
cout << #x << " : " << x << endl; \
RESET;
// 绿色
#define DEBUG2(x) \
GREEN; \
cout << #x << " : " << x << endl; \
RESET;
// 蓝色
#define DEBUG3(x) \
BLUE; \
cout << #x << " : " << x << endl; \
RESET;
// 品红
#define DEBUG4(x) \
MAGENTA; \
cout << #x << " : " << x << endl; \
RESET;
// 青色
#define DEBUG5(x) \
CYAN; \
cout << #x << " : " << x << endl; \
RESET;
// 黄色
#define DEBUG6(x) \
YELLOW; \
cout << #x << " : " << x << endl; \
RESET;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pdd;
typedef pair<ll, int> pli;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;
typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;
typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pli> vpli;
typedef vector<pss> vpss;
typedef vector<ti3> vti3;
typedef vector<tl3> vtl3;
typedef vector<tld3> vtld3;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef priority_queue<string> pqs;
typedef priority_queue<pii> pqpii;
typedef priority_queue<psi> pqpsi;
typedef priority_queue<pll> pqpll;
typedef priority_queue<psi> pqpsl;
typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;
typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template <typename T>
inline T read()
{
T x = 0;
int y = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
{
if (ch == '-')
y = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * y;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x >= 10)
{
write(x / 10);
}
putchar(x % 10 + '0');
}
/*#####################################BEGIN#####################################*/
void solve()
{
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
A. Profitable Interest Rate
每个测试的时间限制:1秒
每个测试的内存限制:256兆字节
Alice 有 a a a 个硬币。她可以开立一个名为“盈利”的银行存款,但开立此存款所需的最低金额为 b b b 个硬币。
还有一个名为“无利可图”的存款,可以用任意数量的硬币开立。Alice 注意到,如果她用 x x x 个硬币开立“无利可图”存款,则开立“盈利”存款所需的最低金额会减少 2 x 2x 2x 个硬币。但是,这些硬币以后不能存入“盈利”存款。
如果 Alice 首先将一定数量的硬币(可能是 0)存入“无利可图”存款,请帮助她确定她可以存入“盈利”存款的最大硬币数量。如果 Alice 永远无法开立“盈利”存款,则输出 0。
输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 ) (1 \leq t \leq 10^4) (1≤t≤104) — 测试用例的数量。测试用例的描述如下。
每个测试用例的一行包含两个整数 a a a 和 b b b ( 1 ≤ a , b ≤ 1 0 9 ) (1 \leq a, b \leq 10^9) (1≤a,b≤109) — Alice 拥有的硬币数量以及开立“盈利”存款所需的初始最低金额。
输出
对于每个测试用例,输出一个整数 — Alice 可以存入“盈利”存款的最大硬币数量。如果 Alice 永远无法开立“盈利”存款,则输出 0。
示例
输入
5
10 5
7 9
5 100
1 1
1 2
输出
10
5
0
1
0
注意
在第一个测试案例中, a ≥ b a \geq b a≥b,因此 Alice 可以立即用所有 10 个硬币开立“盈利”存款。
在第二个测试案例中,Alice 可以用 2 个硬币开立“无盈利”存款。然后她将剩下 5 个硬币,但开立“盈利”存款所需的最低金额将减少 4 个硬币,使其等于 5 个硬币。因此,Alice 将能够用 5 个硬币开立“盈利”存款。
在第三个测试案例中,Alice 将无法开立“盈利”存款。
解题思路
根据题目要求列出公式 a − x = b − 2 x a-x=b-2x a−x=b−2x,移项得 x = b − a x=b-a x=b−a。再特判一下极端情况即可。
代码实现
void solve()
{
ll a, b;
cin >> a >> b;
if (a >= b)
{
cout << a << endl;
return;
}
else if (a * 2 <= b)
{
cout << 0 << endl;
return;
}
int d = b - a;
cout << a - d << endl;
}
B. Buying Lemonade
每个测试的时间限制:1秒
每个测试的内存限制:256兆字节
输入:标准输入
输出:标准输出
有一台出售柠檬水的自动售货机。这台机器共有 n n n 个槽位。你知道最初,第 i i i 个槽位包含 a i a_i ai 罐柠檬水。机器上还有 n n n 个按钮,每个按钮对应一个槽位,每个槽位对应一个按钮。不幸的是,按钮上的标签已经磨损,所以你不知道哪个按钮对应哪个槽位。
当你按下与第 i i i 个槽位对应的按钮时,会发生以下两种情况之一:
- 如果第 i i i 个槽位中有一罐柠檬水,它会掉出来,你会拿走它。此时,第 i i i 个槽位中的罐子数量减少了 1 1 1。
- 如果第 i i i 个槽位中没有剩余的柠檬水罐,则不会掉出任何东西。
按下按钮后,罐子会快速掉出,因此无法追踪它从哪个槽位掉落。槽位的内容隐藏在您的视野之外,因此您无法看到每个槽位中剩余的罐子数量。您唯一知道的是槽位中的初始罐子数量: a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an。
确定需要按下的最少按钮次数,以保证您至少收到 k k k 罐柠檬水。
请注意,您可以根据是否收到罐子来调整按钮按下时的策略。保证机器中总共至少有 k k k 罐柠檬水。换句话说, k ≤ a 1 + a 2 + … + a n k \leq a_1 + a_2 + \ldots + a_n k≤a1+a2+…+an。
输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104) — 测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含两个整数 n n n 和 k k k ( 1 ≤ n ≤ 2 ⋅ 1 0 5 , 1 ≤ k ≤ 1 0 9 1 \leq n \leq 2 \cdot 10^5, 1 \leq k \leq 10^9 1≤n≤2⋅105,1≤k≤109) — 机器中的插槽数量和所需的柠檬水罐数。
每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109) — 插槽中的罐子数量。
保证有 k ≤ a 1 + a 2 + … + a n k \leq a_1 + a_2 + \ldots + a_n k≤a1+a2+…+an ,这意味着机器中至少有 k k k 罐柠檬水。
保证所有测试用例的 n n n 之和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105。
输出
对于每个测试用例,输出一个整数 — 保证您收到至少 k k k 罐柠檬水所需的最少按钮按下次数。
示例
输入
5
2 1
1 1
2 2
1 2
3 4
2 1 3
10 50
1 1 3 8 8 9 12 13 27 27
2 1000000000
1000000000 500000000
输出
1
2
5
53
1000000000
注意
在第一个测试用例中,我们可以简单地按下第一个按钮并收到一罐柠檬水。
在第二个测试用例中,我们可以按下每个按钮一次,确保我们收到 2 2 2 罐柠檬水。请注意,如果我们只是按下一个按钮两次,我们可能不会幸运,那样的话按钮可能对应第一个槽位,在这种情况下我们只会收到 1 1 1 罐柠檬水。
在第三个测试用例中,最优策略之一如下:
- 按下第一个按钮两次。第一次按下后,肯定会掉出一罐柠檬水。然后有两种选择:
- 如果第二次按下后没有掉出柠檬水,我们知道这个按钮一定对应第二个槽位,因为 a 2 = 1 a_2=1 a2=1 且 a 1 , a 3 > 1 a_1, a_3 > 1 a1,a3>1。然后我们可以按下第二个按钮两次,第三个按钮一次。由于 a 1 , a 3 ≥ 2 a_1, a_3 \geq 2 a1,a3≥2,我们肯定会在这三次按下中收到三罐柠檬水。因此,在 5 5 5 次按下后,我们将获得 4 4 4 罐柠檬水。
- 如果第二次按下后掉出了一罐柠檬水,我们可以按下第二个按钮一次,第三个按钮一次。在每次按下中,我们肯定会收到一罐柠檬水。因此,在 4 4 4 次按下后,我们将获得 4 4 4 罐柠檬水。
可以证明,仅用 4 4 4 次按下是无法保证收到 4 4 4 罐柠檬水的,因此答案是 5 5 5。
解题思路
对于任意一个按钮,如果我们按下之后它不再出水,那我们肯定不会再去按它。为了让我们按按钮的总次数尽量少,我们需要不去按下已经不出水的按钮。
一个显然的策略是一轮按下所有还出水的按钮即 a i > n o w a_i\gt now ai>now, n o w now now为已经按下的次数。
为了方便维护 a i > n o w a_i>now ai>now,我们可以将 a a a升序排序,如果 a i ≤ n o w a_i\le now ai≤now,我们可以把 i i i往后挪直达 a j > n o w a_j>now aj>now
观察到 a i ≤ 1 0 9 a_i\le10^9 ai≤109,不能一轮一轮模拟,需要使用除法加速。
代码实现
void solve()
{
ll n, k;
cin >> n >> k;
vl a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
sort(all(a)); // 将罐子数量排序
ll len = n; // 剩余槽位数量
ll ans = 0; // 按钮按下的总次数
ll now = 0; // 当前获得的柠檬水数量
int i = 0; // 当前槽位索引
while (k)
{
// 处理当前槽位
while (a[i] <= now) // 如果当前槽位的罐子数量小于等于已获得的数量
{
i++;
ans++; // 按下按钮
len--; // 剩余槽位数量减少
}
// 计算当前槽位能提供的柠檬水
if (k >= len) // 如果我们需要的数量大于或等于剩余槽位数量
{
ll t = k / len; // 每个槽位至少要按下 t 次
ll cnt = min(t, a[i] - now); // 当前槽位能提供的数量
ans += cnt * len; // 计算总的按钮按下次数
now += cnt; // 更新已获得的数量
k -= cnt * len; // 更新还需的数量
}
else // 如果需要的数量小于剩余槽位数量
{
ans += k; // 直接按下 k 次
k = 0; // 更新还需的数量
}
}
cout << ans << endl; // 输出结果
}
C. Concatenation of Arrays
时间限制:每个测试 2 秒
内存限制:每个测试 256 兆字节
输入:标准输入
输出:标准输出
给定 n n n 个数组 a 1 , … , a n a_1, \ldots, a_n a1,…,an。每个数组的长度为 2 2 2,因此 a i = [ a i , 1 , a i , 2 ] a_i = [a_{i,1}, a_{i,2}] ai=[ai,1,ai,2]。您需要将这些数组连接成一个长度为 2 n 2n 2n 的数组,以使结果数组中的逆序对数量最小化。请注意,您不需要计算实际的逆序对数量。
更正式地说,您需要选择一个长度为 n n n 的排列 p p p,以便数组 b = [ a p 1 , 1 , a p 1 , 2 , a p 2 , 1 , a p 2 , 2 , … , a p n , 1 , a p n , 2 ] b = [a_{p_1,1}, a_{p_1,2}, a_{p_2,1}, a_{p_2,2}, \ldots, a_{p_n,1}, a_{p_n,2}] b=[ap1,1,ap1,2,ap2,1,ap2,2,…,apn,1,apn,2] 包含尽可能少的逆序对。
逆序对的定义是:数组 c c c 中的逆序对数量是索引对 i i i 和 j j j 的数量,使得 i < j i < j i<j 且 c i > c j c_i > c_j ci>cj。
一个长度为 n n n 的排列是一个由 n n n 个不同整数组成的数组,这些整数从 1 1 1 到 n n n 以任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4] 是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2] 不是一个排列( 2 2 2 在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是排列( n = 3 n=3 n=3 但是数组中有 4 4 4)。
输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104) — 测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105) — 数组的数量。
接下来的每一行包含两个整数 a i , 1 a_{i,1} ai,1 和 a i , 2 a_{i,2} ai,2 ( 1 ≤ a i , j ≤ 1 0 9 1 \leq a_{i,j} \leq 10^9 1≤ai,j≤109) — 第 i i i 个数组的元素。
保证所有测试用例的 n n n 的总和不超过 1 0 5 10^5 105。
输出
对于每个测试用例,输出 2 n 2n 2n 个整数 — 您获得的数组的元素。如果有多个解决方案,则输出其中任何一个。
示例
输入
4
2
1 4
2 3
3
3 2
4 3
2 1
5
5 10
2 3
9 6
4 1
8 7
1
10 20
输出
2 3 1 4
2 1 3 2 4 3
4 1 2 3 5 10 8 7 9 6
10 20
注意
在第一个测试用例中,我们以顺序 2 , 1 2, 1 2,1 连接数组。考虑结果数组 b = [ 2 , 3 , 1 , 4 ] b = [2, 3, 1, 4] b=[2,3,1,4] 中的逆序对:
- i = 1 , j = 3 i=1, j=3 i=1,j=3,因为 b 1 = 2 > 1 = b 3 b_1=2 > 1=b_3 b1=2>1=b3;
- i = 2 , j = 3 i=2, j=3 i=2,j=3,因为 b 2 = 3 > 1 = b 3 b_2=3 > 1=b_3 b2=3>1=b3。
因此,逆序对的数量是 2 2 2。可以证明这是可能的最小逆序对数量量。
在第二个测试用例中,我们以顺序 3 , 1 , 2 3, 1, 2 3,1,2 连接数组。考虑结果数组 b = [ 2 , 1 , 3 , 2 , 4 , 3 ] b = [2, 1, 3, 2, 4, 3] b=[2,1,3,2,4,3] 中的逆序对:
- i = 1 , j = 2 i=1, j=2 i=1,j=2,因为 b 1 = 2 > 1 = b 2 b_1=2 > 1=b_2 b1=2>1=b2;
- i = 3 , j = 4 i=3, j=4 i=3,j=4,因为 b 3 = 3 > 2 = b 4 b_3=3 > 2=b_4 b3=3>2=b4;
- i = 5 , j = 6 i=5, j=6 i=5,j=6,因为 b 5 = 4 > 3 = b 6 b_5=4 > 3=b_6 b5=4>3=b6。
因此,逆序对的数量是 3 3 3。可以证明这是可能的最小逆序对数量量。
在第三个测试用例中,我们以顺序 4 , 2 , 1 , 5 , 3 4, 2, 1, 5, 3 4,2,1,5,3 连接数组。
解题思路
为了确保连接后的数组尽可能减少逆序对数量,我们按以下规则进行排序。
-
最小值优先:首先根据每个数组的最小值进行排序,可以确保在连接数组时,较小的元素会出现在前面,从而减少后面元素大于前面元素的情况。
-
最大值次之:如果两个数组的最小值相同,则根据最大值进行排序。这是为了在最小值相同的情况下,进一步控制元素的相对顺序,确保较大的元素不会在较小元素之前出现。
考虑两个数组 $ a = [x_1, y_1] $ 和 $ b = [x_2, y_2] $,其中 $ x_1 \leq y_1 $ 和 $ x_2 \leq y_2 $。
- 排序依据:
- 如果 $ x_1 < x_2 $,那么在连接时 $ x_1 $ 会在 $ x_2 $ 之前,减少逆序对。
- 如果 $ x_1 = x_2 $,那么需要看 $ y_1 $ 和 $ y_2 $ 的大小:
- 如果 $ y_1 < y_2 $,那么连接后 $ y_1 $ 会在 $ y_2 $ 之前,继续减少逆序对。
- 如果 $ y_1 \geq y_2 $,则可能会增加逆序对,但由于最小值相同,造成的影响相对较小。
假设我们有以下数组:
- $ a_1 = [1, 4] $
- $ a_2 = [2, 3] $
如果我们不进行排序,直接连接可能得到 $ [1, 4, 2, 3] $,这里会有逆序对(例如 $ 4 > 2 $ 和 $ 4 > 3 $),逆序对数量较多。
如果我们按照最小值排序,得到的顺序是 $ a_2, a_1 $,连接后得到 $ [2, 3, 1, 4] $,这样逆序对数量明显减少。
代码实现
void solve()
{
int n;
cin >> n;
vpii a(n);
for (int i = 0; i < n; ++i)
{
cin >> a[i].ft >> a[i].sd;
}
sort(all(a), [&](const pii &x, const pii &y)
{
if(min(x.ft, x.sd) != min(y.ft, y.sd))
return min(x.ft, x.sd) < min(y.ft, y.sd);
return max(x.ft, x.sd) < max(y.ft, y.sd); });
for (int i = 0; i < n; ++i)
{
cout << a[i].ft << " " << a[i].sd << " \n"[i == n - 1];
}
}
D. Skipping
时间限制:每个测试 2 秒
内存限制:每个测试 256 兆字节
输入:标准输入
输出:标准输出
现在已经是3024年,题目的想法早已用尽,奥林匹克竞赛现在以经过修改的个人形式举行。奥林匹克竞赛由 n n n 个问题组成,编号从 1 1 1 到 n n n。第 i i i 个问题有自己的分数 a i a_i ai 和特定参数 b i b_i bi ( 1 ≤ b i ≤ n 1 \leq b_i \leq n 1≤bi≤n)。
最初,测试系统会给参与者第一个问题。当参与者得到第 i i i 个问题时,他们有两个选择:
- 提交问题并获得 a i a_i ai 分;
- 跳过这个问题,在这种情况下他们将永远无法提交它。
然后,测试系统从索引为 j j j 的问题中为参与者选择下一个问题:
- 如果他提交了第 i i i 个问题,则查看索引为 j < i j < i j<i 的问题;
- 如果他跳过了第 i i i 个问题,则查看索引为 j ≤ b i j \leq b_i j≤bi 的问题。
在这些问题中,它选择索引为最大的问题,该问题以前没有给过参与者(他以前既没有提交过也没有跳过过)。如果没有这样的问题,则参与者的比赛结束,他们的结果等于所有提交问题的分数总和。特别是,如果参与者提交了第一个问题,则他们的比赛结束。请注意,参与者最多收到每个问题一次。
Prokhor 已经为奥林匹克竞赛做好了充分的准备,现在他可以提交任何问题。帮助他确定他可以获得的最高分数。
输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 5 1 \leq t \leq 10^5 1≤t≤105) — 测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 4 ⋅ 1 0 5 1 \leq n \leq 4 \cdot 10^5 1≤n≤4⋅105) — 奥林匹克竞赛中的问题数量。
第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109) — 问题的分数。
第三行包含 n n n 个整数 b 1 , b 2 , … , b n b_1, b_2, \ldots, b_n b1,b2,…,bn ( 1 ≤ b i ≤ n 1 \leq b_i \leq n 1≤bi≤n) — 问题的参数。
保证所有测试用例的 n n n 的总和不超过 4 ⋅ 1 0 5 4 \cdot 10^5 4⋅105。
输出
对于每个测试用例,输出一个整数——Prokhor 可以获得的最大分数。
示例
输入
4
2
15 16
2 1
5
10 10 100 100 1000
3 4 1 1 1
3
100 49 50
3 2 2
4
100 200 300 1000
2 3 4 1
输出
16
200
100
1000
注意
在第一个测试用例中,Prokhor 可以跳过第一个问题;然后他将收到索引为 b 1 = 2 b_1=2 b1=2 的问题。Prokhor 可以提交它并获得 a 2 = 16 a_2=16 a2=16 分。之后,比赛将结束,因为 Prokhor 已经收到所有问题。请注意,如果 Prokhor 提交第一个问题,他将获得 a 1 = 15 a_1=15 a1=15 分,但比赛会立即结束。
在第二个测试用例中,Prokhor 可以跳过第一个问题;然后他将收到索引为 b 1 = 3 b_1=3 b1=3 的问题。Prokhor 可以提交它并获得 a 3 = 100 a_3=100 a3=100 分。之后,Prokhor 将收到第二个问题,他可以跳过以接收索引为 b 2 = 4 b_2=4 b2=4 的问题。Prokhor 可以提交第四个问题并获得另一个 a 4 = 100 a_4=100 a4=100 分。之后,比赛结束,因为 Prokhor 已经收到所有索引不超过 4 4 4 的问题。因此,Prokhor 将获得总共 200 200 200 分。
在第三个测试用例中,Prokhor 可以提交第一个问题并获得 100 100 100 分,之后比赛会立即结束。
解题思路
观察题目发现,我们最优的策略一定是经过一系列点 p 1 , p 2 , p 3 … p m p_1,p_2,p_3\dots p_m p1,p2,p3…pm到达 p m p_m pm后停下,然后在 p m p_m pm跳转到所有 i ∉ P , i < p m i\notin P,i<p_m i∈/P,i<pm的点上获取它们的分数,且 ∀ i < m , p i < p m \forall i\lt m,p_i\lt p_m ∀i<m,pi<pm。如果 ∃ p j > p m \exist p_j\gt p_m ∃pj>pm那我们在 p j p_j pj停下一定不劣于在 p m p_m pm停下。
答案 ans = min ( prefix p m − ∑ i = 1 m a p i ) , 1 ≤ p m ≤ n \text{ans}=\text{min}( \text{prefix}_{p_m}-\sum^{m}_{i=1}a_{p_i}),1\le p_m\le n ans=min(prefixpm−∑i=1mapi),1≤pm≤n
所以我们可以建图,然后跑最短路,算出到达每一个点的最小花费。
对于第一种操作,对 i → i − 1 i\rightarrow i-1 i→i−1建边,其边权为 0 0 0
对于第二种操作,对 i → b i i\rightarrow b_i i→bi建边,其边权为 a i a_i ai
然后跑一遍dijkstra算出到达每一个点的最小代价
然后枚举所有可能的终点即可。
这题还有dp的做法,我有空补补。
代码实现
void solve()
{
int n;
cin >> n;
vi a(n + 1);
vl pre(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
vi b(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> b[i];
}
vvi adj(n + 1);
vvl w(n + 1);
for (int i = 1; i <= n; i++)
{
adj[i].pb(b[i]);
w[i].pb(a[i]);
if (i != 1)
{
adj[i].pb(i - 1);
w[i].pb(0);
}
}
vl dis(n + 1, infll);
vb vis(n + 1);
dis[1] = 0;
auto dijkstra = [&]()
{
priority_queue<pll, vpll, greater<pll>> q;
q.push({
0, 1});
while (!q.empty())
{
auto t = q.top();
q.pop();
ll d = t.ft;
int u = t.sd;
if (vis[u])
continue;
vis[u] = true;
for (int i = 0; i < adj[u].size(); i++)
{
int v = adj[u][i];
ll cost = d + w[u][i];
if (dis[v] > cost)
{
dis[v] = cost;
q.push({
cost, v});
}
}
}
};
dijkstra();
ll ans = 0;
for (int i = 1; i <= n; i++)
{
ans = max(ans, pre[i] - dis[i]);
}
cout << ans << endl;
}
E E E不会写,看不懂一点,最近这几场掉大分,本来差12分上紫,结果狂掉270分,差点掉出蓝了,尝尝卡 B B B和 C C C,导致没时间写后面的了,人快麻了。