2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest 个人题解(A,B,C,G,J,K,L,N)
Dashboard - 2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest (Unrated, Online Mirror, ICPC Rules, Preferably Teams) - Codeforces
难度排序
由低到高
N、J、L、C、A、G、K、B。
火车头
#include <bits/stdc++.h>
using namespace std;
#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 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" // 重置
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<ll, int> pli;
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<vi> vvi;
typedef vector<vl> vvl;
// 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. Bonus Project
有一个由 n n n 名软件工程师组成的团队,编号从 1 到 n n n。他们的老板承诺,如果他们完成一个额外的项目,就会给他们发奖金。该项目总共需要 k k k 个工作单位。答应给第 i i i 位工程师的奖金是 a i a_i ai 布勒斯。老板没有给工程师分配具体任务,而是希望每个工程师都能自愿完成某个整数的工作单位。只有当项目完成时,整个团队才会获得奖金;换句话说,如果项目中自愿完成的工作单位总数大于或等于 k k k,整个团队才会获得奖金。
每位工程师可完成的工作量不受限制。不过,所有工程师都会珍惜自己的劳动成果。第 i i i 位工程师估计他们的一个工作单位为 b i b_i bi 布尔。如果支付了奖金,那么第 i i i 位工程师完成 c c c 个单位的工作所获得的收益 s i s_i si 定义为 s i = a i − c ⋅ b i s_i = a_i - c \cdot b_i si=ai−c⋅bi。如果不支付奖金,工程师将不会自愿完成任何工作。
工程师们在一起工作多年,因此他们知道奖金将如何分配以及同事们对劳动的重视程度。也就是说,团队中的每个工程师都知道所有的 a i a_i ai 和所有的 b i b_i bi。
工程师们都渴望获得奖金,因此他们之间商定了以下工作分配流程:
第一位工程师说:“我将完成
c
1
c_1
c1 个工作单位”,其中
c
1
c_1
c1 是一个非负整数;
然后,第二个工程师说:“我将完成
c
2
c_2
c2 个工作单位”,其中
c
2
c_2
c2 是一个非负整数;
…依此类推;
最后,第
n
n
n 位工程师说:“我将完成
c
n
c_n
cn 个工作单位”,其中
c
n
c_n
cn 是一个非负整数。
每个工程师都会发出
c
i
c_i
ci 的声音,使自己的利益
s
i
s_i
si 最大化。如果预期收益为零,工程师仍会同意工作以获得经验,并帮助同事获得奖金。但是,如果由于某种原因预期收益为负(工程师需要完成过多的工作或项目无法完成),该工程师将根本不工作(完成零工作量)。
鉴于每个工程师的行为都是完美的,你的任务是找出每个工程师所表达的数字 c i c_i ci。
输入
第一行包含两个整数
n
n
n 和
k
k
k (
1
≤
n
≤
1000
1 \leq n \leq 1000
1≤n≤1000 ;
1
≤
k
≤
1
0
6
1 \leq k \leq 10^6
1≤k≤106 )——分别是公司的工程师人数和项目所需的工作单位数量。
第二行包含 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 ),其中 a i a_i ai 是项目完成后将支付给第 i i i 个工程师的奖金。
第三行包含 n n n 个整数 b 1 , b 2 , … , b n b_1, b_2, \ldots, b_n b1,b2,…,bn ( 1 ≤ b i ≤ 1000 1 \leq b_i \leq 1000 1≤bi≤1000 ),其中 b i b_i bi 是第 i i i 位工程师的工作单位成本。
输出
打印
n
n
n 个整数
c
1
,
c
2
,
…
,
c
n
c_1, c_2, \ldots, c_n
c1,c2,…,cn (
0
≤
c
i
≤
k
0 \leq c_i \leq k
0≤ci≤k )——在每个工程师都表现最优的情况下,每个工程师完成的工作量。请注意,答案是唯一的。
示例
输入
3 6
4 7 6
1 2 3
输出
1 3 2
输入
3 12
4 7 6
1 2 3
输出
0 0 0
输入
3 11
6 7 8
1 2 3
输出
6 3 2
提示
在第一个示例中,工程师们在他们之间分配了工作并获得了奖金,尽管第三位工程师的收益为零。
在第二个示例中,奖金项目需要的工作单位太多,因此工程师们不工作更有利。
解题思路
由于工作分配流程是从前往后的,因此越后面的人,选择权越小。
倒着枚举,将工作尽可能分配给后面的人即可。
搞不定数据范围设置成1000干啥,吓人吗
实现代码
void solve()
{
int n, k;
cin >> n >> k;
vi a(n), b(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
for (int i = 0; i < n; i++)
{
cin >> b[i];
}
vi ans(n);
int now = 0;
for (int i = n - 1; i >= 0; i--)
{
int num = a[i] / b[i];
num = min(num, k - now);
ans[i] = num;
now += num;
if (now == k)
break;
}
if (now < k)
{
for (int i = 0; i < n; i++)
{
cout << "0 ";
}
cout << "\n";
}
else
{
for (int i = 0; i < n; i++)
{
cout << ans[i] << " \n"[i == n - 1];
}
}
}
B. Make It Equal
给你一个大小为 n n n 的整数数组 a a a。数组元素的编号从 1 到 n n n。
您可以执行以下任意次数的操作(可能为 0 次):从 1 到 n n n 之间选择一个索引 i i i;将 a i a_i ai 减少 2,并将 a ( i m o d n ) + 1 a_{(i \mod n) + 1} a(imodn)+1 增加 1。
执行这些操作后,数组中的所有元素都应是非负等整数。
你的任务是计算最少需要执行的运算次数。
输入
第一行包含一个整数
t
t
t (
1
≤
t
≤
1
0
4
1 \leq t \leq 10^4
1≤t≤104 )——测试用例数。
每个测试用例的第一行包含一个整数 n n n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \leq n \leq 2 \cdot 10^5 2≤n≤2⋅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 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105。
输出
对于每个测试用例,打印一个整数——你必须执行的最小操作数。如果不可能使数组中的所有元素都相等,则打印 -1。
示例
输入
3
2
1 1
3
1 3 2
4
2 1 2 6
输出
0
-1
3
解题思路
观察发现,如果一个数组全部元素相等,那么我们可以对所有元素进行一次操作,从而让所有元素都减 1 1 1 。
因此,如果一个数组能够通过操作使得数组元素全部相等,且使得相等的最大元素为 x x x 那么,我们一定也可以构造出 [ 1 , x − 1 ] [1,x-1] [1,x−1] 相等的数组。
因此,最后的相等元素 x x x 具有二段性,所以我们可以二分 x x x 是否为最大的最终相等元素。
考虑设计检查函数。
我们可以对所有数进行操作,使得 $\forall a_i \le x $,然后检查是否 $\forall a_i = a_{i+1} $。
对于一个数 a i a_i ai 要把它变为 x x x ,当 a i ≤ x a_i \le x ai≤x 肯定是不操作;当 a i > x a_i>x ai>x 时,如果 a i − x a_i-x ai−x 为奇数,就先对 a i − 1 a_{i-1} ai−1 操作一次,让其变成偶数,然后对其操作 a i − x 2 \frac{a_{i}-x}{2} 2ai−x 次。
如果只操作一遍,也许任然存在 a i > x a_i\gt x ai>x ,因此我们需要循环操作 直到$\forall a_i \le x $。
看上去可能超时,考虑极限情况 x = 0 , ∀ a i = 1 0 9 x=0,\forall a_i=10^9 x=0,∀ai=109。
由于我们设计的操作函数每次会将 a i a_i ai 的值减半加到 a i + 1 a_{i+1} ai+1 ,因此对于整体而言,我们每次都减去了 a i 2 \frac{a_i}{2} 2ai ,极限情况下数组和为 n × a i n\times a_i n×ai ,因此我们总共会操作 l o g 2 a i × n log_2{a_i}\times n log2ai×n 。因此,我们检查函数的时间复杂度为 O ( n l o g V ) O(nlogV) O(nlogV) 。
实现代码
void solve()
{
int n;
cin >> n;
vi a(n);
ll sum = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
sum += a[i];
}
auto check = [&](int x) -> bool
{
vi temp = a;
while (1)
{
bool flag = true;
for (int i = 0; i < n; i++)
{
if (temp[i] <= x)
continue;
flag = false;
if ((temp[i] - x) & 1)
{
temp[(i - 1 + n) % n] -= 2;
temp[i]++;
}
temp[(i + 1) % n] += (temp[i] - x) / 2;
temp[i] = x;
}
if (flag)
break;
}
for (int i = 0; i < n; i++)
{
if (temp[i] != x)
return false;
}
return true;
};
int l = 0, r = (sum + n - 1) / n;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (check(mid))
l = mid;
else
r = mid - 1;
}
if (check(r))
cout << sum - 1ll * r * n << "\n";
else
cout << "-1\n";
}
C. DIY
给你一个由 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an 组成的列表。你需要从列表中选取 8 个元素作为四个点的坐标。这四个点应该是边平行于坐标轴的矩形的角。您的任务是选取坐标,使得到的矩形具有尽可能大的面积。矩形可以是退化矩形,即其面积可以是 0。每个整数在列表中出现的次数不限(或更少)。
输入
第一行包含一个整数
t
t
t (
1
≤
t
≤
25000
1 \leq t \leq 25000
1≤t≤25000 )——测试用例数。
每个测试用例的第一行包含一个整数 n n n ( 8 ≤ n ≤ 2 ⋅ 1 0 5 8 \leq n \leq 2 \cdot 10^5 8≤n≤2⋅105 )。
每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( − 1 0 9 ≤ a i ≤ 1 0 9 -10^9 \leq a_i \leq 10^9 −109≤ai≤109 )。
输入的附加限制:所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105。
输出
对于每个测试用例,打印答案如下:
如果不可能构造出符合语句限制条件的矩形,则打印包含 NO(不区分大小写)字样的一行;
否则,在第一行打印 YES(不区分大小写)。在第二行中,打印 8 个整数
x
1
,
y
1
,
x
2
,
y
2
,
x
3
,
y
3
,
x
4
,
y
4
x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4
x1,y1,x2,y2,x3,y3,x4,y4——矩形各角的坐标。您可以按照任意顺序打印角的坐标。
示例
输入
3
16
-5 1 1 2 2 3 3 4 4 5 5 6 6 7 7 10
8
0 0 -1 2 2 1 1 3
8
0 0 0 0 0 5 0 5
输出
YES
1 2 1 7 6 2 6 7
NO
YES
0 0 0 5 0 0 0 5
解题思路
对于是否可以构建出矩阵,我们只需要检查是否存在 4 4 4 对以上相同的数即可。
对于如何找寻最大矩形,如果所以数的数量都大于 4 4 4 ,那么我们一定是贪心的选择最大和最小的数作为矩形的边角,由此推得,我们一定是选择最大和次大,最小和次小的数对来构造矩形。
实现代码
void solve()
{
int n;
cin >> n;
map<int, int> mp;
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
mp[x]++;
}
int cnt = 0;
for (auto x : mp)
{
cnt += x.sd / 2;
}
if (cnt < 4)
{
NO;
return;
}
int mx1 = -inf;
int mx2 = -inf;
int mn1 = inf;
int mn2 = inf;
for (auto x : mp)
{
if (x.sd >= 4)
{
mx1 = max(mx1, x.ft);
mx2 = max(mx2, x.ft);
mn1 = min(mn1, x.ft);
mn2 = min(mn2, x.ft);
}
else if (x.sd >= 2)
{
if (x.ft > mx1)
{
mx2 = mx1;
mx1 = x.ft;
}
else if (x.ft > mx2)
{
mx2 = x.ft;
}
if (x.ft < mn1)
{
mn2 = mn1;
mn1 = x.ft;
}
else if (x.ft < mn2)
{
mn2 = x.ft;
}
}
}
YES;
cout << mn1 << " " << mn2 << " " << mn1 << " " << mx1 << " " << mx2 << " " << mn2 << " " << mx2 << " " << mx1 << "\n";
}
G. Guess One Character
这是一个交互式问题。您必须在打印完每一行后立即使用 flush 操作。例如,在 C++ 中应使用函数 fflush(stdout) 或 cout.flush(),在 Java 或 Kotlin 中应使用 System.out.flush(),在 Python 中应使用 sys.stdout.flush()。
陪审团有一个由字符 0 和/或 1 组成的字符串 s s s。该字符串的长度为 n n n。
您可以进行以下查询:
- t t t — " t t t 作为连续子串在 s s s 中出现了多少次?" 在这里, t t t 应该是一个由字符 0 和/或 1 组成的字符串;其长度至少为 1 ,最多为 n n n。例如,如果字符串 s s s 是 111011,而字符串 t t t 是 11,那么查询的回复就是 3。
您必须通过不超过 3 的查询猜出字符串 s s s 中的至少一个字符。需要注意的是,给出答案并不算一次询问。
在每个测试和每个测试用例中,字符串 s s s 都是事先固定的。
互动
最初,陪审团程序发送一个整数
t
t
t (
1
≤
t
≤
1000
1 \leq t \leq 1000
1≤t≤1000 )——测试用例数。
在每个测试用例开始时,陪审团程序发送一个整数 n n n ( 2 ≤ n ≤ 50 2 \leq n \leq 50 2≤n≤50 )——字符串的长度。
之后,您的程序可以通过打印以下一行向陪审团程序提交查询(打印完一行后不要忘记刷新输出!):
1
t
t
t 表示询问 "
s
s
s 中的连续子串
t
t
t 出现了多少次?"
对于每个查询,陪审团都会在单独一行中打印一个整数。它要么是查询的答案,如果查询是正确的,并且没有超出查询限制;或者是整数 −1 ,如果您的查询不正确(例如,未满足约束
1
≤
∣
t
∣
≤
n
1 \leq |t| \leq n
1≤∣t∣≤n 或字符串
t
t
t 包含 0 和 1 以外的字符),或者您在处理当前测试用例时提出了太多查询。
要提交答案,您的程序应按以下格式发送一行(打印完一行后不要忘记刷新输出!):
0
i
i
i
c
c
c,其中
1
≤
i
≤
n
1 \leq i \leq n
1≤i≤n 和
c
c
c 要么为 0 要么为 1,即
s
i
=
c
s_i = c
si=c。
如果您的猜测正确,陪审团程序将在单独一行中打印一个整数 1 ,表示您可以进入下一个测试用例(如果是最后一个测试用例,则终止程序),并且您提出的询问次数将被重置。如果不正确,陪审团程序将在另一行打印一个整数 −1 。
程序收到 −1 作为响应后,应立即终止。这将导致您的提交收到 “错误答案” 的裁决。如果您的程序没有终止,则您的提交结果为 “未定义”。
示例
输入
3 // 3 测试用例
3 // 字符串长度为 3
1 // 101 出现一次
1 // 猜测正确
2 // 字符串长度为 2
0 // 00 出现零次
0 // 0 出现零次
1 // 猜测正确
2 // 字符串长度为 2
1 // 1 出现一次
0 // 01 出现零次
1 // 猜测正确
输出
1 101 // 查询 101 出现多少次
0 2 0 // 猜测:s[2] 是 0
1 00 // 查询 00 出现多少次
1 0 // 查询 0 出现多少次
0 1 1 // 猜测:s[1] 是 1
1 1 // 查询 1 出现多少次
1 01 // 查询 01 出现多少次
0 2 0 // 猜测:s[2] 是 0
注意
在示例中,有 3 个测试用例:101、11 和 10。请注意,所有注释内容(// 后的内容)不会在实际问题中打印,您也不应打印这些内容。空行也是为了方便您而添加的,陪审团程序不会打印它们,您的解决方案也不应打印任何空行。
解题思路
将字符除串按长度为 1 1 1 进行划分,我们可以得到 0 0 0 和 1 1 1 两种子字符串,按长度为 2 2 2 进行划分,我们可以得到 00 00 00 , 01 01 01 , 11 11 11 和 10 10 10 四种子字符串。
观察发现,如果只存在 00 00 00 和 01 01 01 字符串,那么 0 0 0 子字符串的数量将等于这两种字符串数量相加,那么最后一为一定是 1 1 1 。
实现代码
int query(string s)
{
printf("1 %s\n", s.c_str());
fflush(stdout);
return read<int>();
}
void answer(int pos, char c)
{
printf("0 %d %c\n", pos, c);
fflush(stdout);
}
void solve()
{
int n = read<int>();
int n0 = query("0");
int n00 = query("00");
int n01 = query("01");
if (n00 + n01 == n0)
answer(n, '1');
else
answer(n, '0');
int res = read<int>();
assert(res == 1);
}
J. Waiting for…
Monocarp 正在公交车站等车。不幸的是,有很多人也想乘坐公共汽车。
您会得到一份两类事件的清单:
- B b i B \, b_i Bbi - 一辆有 b i b_i bi 个空座位的公交车到站;
- P p i P \, p_i Ppi - p i p_i pi 人到达车站。
这些事件按时间顺序排列。
当一辆公共汽车到达时,会发生以下情况。公交车站的所有人(除了 Monocarp)都试图进入公交车。如果有足够的空位,他们就都上车。否则,会有一些人留在公交车站(进入公交车的人数等于空余座位数)。
如果所有的人(除了 Monocarp)都进入公交车后还有至少一个空座位,那么 Monocarp 也可以决定进入这辆公交车(但他可能会选择等另一辆公交车)。对于每辆公交车,您都必须确定 Monocarp 是否有可能乘坐该公交车。
输入
第一行包含一个整数
n
n
n - 事件数量。
(
1
≤
n
≤
1
0
3
)
(1 \leq n \leq 10^3)
(1≤n≤103)
然后是 n n n 行。其中第 i i i 行包含第 i i i 个事件的描述,格式如下:
- B b i B \, b_i Bbi ( 1 ≤ b i ≤ 1 0 6 ) (1 \leq b_i \leq 10^6) (1≤bi≤106) - 一辆有 b i b_i bi 个空座位的公交车到站;
- P p i P \, p_i Ppi ( 1 ≤ p i ≤ 1 0 6 ) (1 \leq p_i \leq 10^6) (1≤pi≤106) - p i p_i pi 人到达车站。
输入的其他限制条件:至少有一个 B B B 类型的事件。
输出
对于
B
B
B 类型的每个事件,如果 Monocarp 有可能占用相应的公交车,则打印 “YES”,否则打印 “NO”(不区分大小写)。
示例
输入
10
P 2
P 5
B 8
P 14
B 5
B 9
B 3
P 2
B 1
B 2
输出
YES
NO
NO
YES
NO
YES
解题思路
签到题,按题意进行模拟即可。
实现代码
void solve()
{
int n;
cin >> n;
ll sum = 0;
while (n--)
{
char c;
int x;
cin >> c >> x;
if (c == 'P')
sum += x;
else
{
sum -= x;
if (sum < 0)
{
YES;
sum = 0;
}
else
{
NO;
}
}
}
}
K. Grid Walk
您有一个 n × n n \times n n×n 网格和两个整数 a a a 和 b b b。行和列的编号都是从 1 到 n n n。我们把第 i i i 行和第 j j j 列的交点处的单元格记为 ( i , j ) (i,j) (i,j)。
您现在站在 ( 1 , 1 ) (1,1) (1,1) 单元格,想要移动到 ( n , n ) (n,n) (n,n) 单元格。
假设您现在位于 ( i , j ) (i,j) (i,j) 单元格;如果存在相应的单元格,您可以一步移动到 ( i , j + 1 ) (i,j+1) (i,j+1) 单元格或 ( i + 1 , j ) (i+1,j) (i+1,j) 单元格。
我们将 ( i , j ) (i,j) (i,j) 单元格的成本定义为
c ( i , j ) = gcd ( i , a ) + gcd ( j , b ) c(i,j) = \text{gcd}(i,a) + \text{gcd}(j,b) c(i,j)=gcd(i,a)+gcd(j,b)
(此处, gcd ( x , y ) \text{gcd}(x,y) gcd(x,y) 表示 x x x 和 y y y 的最大公约数)。从 ( 1 , 1 ) (1,1) (1,1) 到 ( n , n ) (n,n) (n,n) 的路径成本是所访问单元格(包括起始单元格和终点单元格)的成本之和。
找出成本最小的路线并打印其成本。
输入
唯一一行包含三个整数
n
n
n、
a
a
a 和
b
b
b
(
2
≤
n
≤
1
0
6
;
1
≤
a
,
b
≤
1
0
6
)
(2 \leq n \leq 10^6; 1 \leq a,b \leq 10^6)
(2≤n≤106;1≤a,b≤106)。
输出
打印一个整数——从
(
1
,
1
)
(1,1)
(1,1) 到
(
n
,
n
)
(n,n)
(n,n) 的最便宜路线的成本。
示例
输入
4 2 4
输出
21
输入
10 210 420
输出
125
注意
第一个示例在上面的图片中描述。
解题思路
观察发现,无论我们如何操作,我们一定至少会加一遍所有的 gcd ( i , a ) \text{gcd}(i,a) gcd(i,a) 和 gcd ( j , b ) \text{gcd}(j,b) gcd(j,b) ,我们可操作的为剩下需要加上的值 v v v。
考虑极限情况: g c d ( n , a ) = 1 gcd(n,a)=1 gcd(n,a)=1 且 g c d ( n , b ) = 1 gcd(n,b)=1 gcd(n,b)=1 ,则我们一定是走边角最优,除了必加值外,我们剩下加上的值都是 1 1 1 。
因此,对于任意 a , b , n a,b,n a,b,n 来说,我们一定是走到从第一行一直向右走,走到最远的 maxi = i \text{maxi}=i maxi=i 使得 g c d ( a , i ) = 1 gcd(a,i)=1 gcd(a,i)=1 ,然后再一直向下走,走到最远的 maxj = j \text{maxj}=j maxj=j 使得 g c d ( b , j ) = 1 gcd(b,j)=1 gcd(b,j)=1 。然后再考虑剩下的位置这么走。
我们设 r n = n − maxi rn=n-\text{maxi} rn=n−maxi , r m = n − maxj rm=n-\text{maxj} rm=n−maxj ,可以发现 r n rn rn 和 r m rm rm 都比较小。
因为质数和任何数都互质,且质数在 1 0 6 10^6 106 内分布较为稠密,质数之间的最大间隔不会超过 200 200 200,因此 r n < 200 , r m < 200 rn \lt 200,rm \lt 200 rn<200,rm<200。可以直接进行 n 2 n^2 n2 的 dp 。
状态转移方程: d p [ i ] [ j ] = min ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + gcd ( i , a ) + gcd ( j , b ) dp[i][j]=\min(dp[i-1][j],dp[i][j-1])+\gcd(i,a)+\gcd(j,b) dp[i][j]=min(dp[i−1][j],dp[i][j−1])+gcd(i,a)+gcd(j,b)
实现代码
void solve()
{
int n, a, b;
cin >> n >> a >> b;
int maxi = 1;
vi ga(n + 1);
for (int i = 1; i <= n; i++)
{
ga[i] = __gcd(i, a);
if (ga[i] == 1)
maxi = i;
}
int maxj = 1;
vi gb(n + 1);
for (int i = 1; i <= n; i++)
{
gb[i] = __gcd(i, b);
if (gb[i] == 1)
maxj = i;
}
int ans = maxi + maxj - 2;
for (int i = 1; i <= maxi; i++)
{
ans += ga[i];
}
for (int i = 1; i <= maxj; i++)
{
ans += gb[i];
}
int rn = n - maxi;
int rm = n - maxj;
vvi dp(rn + 1, vi(rm + 1, inf));
dp[0][0] = 0;
for (int i = 0; i <= rn; i++)
{
for (int j = 0; j <= rm; j++)
{
if (i > 0)
dp[i][j] = min(dp[i][j], dp[i - 1][j] + ga[maxi + i] + gb[maxj + j]);
if (j > 0)
dp[i][j] = min(dp[i][j], dp[i][j - 1] + ga[maxi + i] + gb[maxj + j]);
}
}
ans += dp[rn][rm];
cout << ans << "\n";
}
L. Bridge Renovation
最近,Monocarp 开始担任他家附近一个公园的园长。公园很大,甚至有一条小河把它分成几个区域。河上建有几座桥。其中有三座桥特别老旧,需要修理。
三座桥的长度相同,但宽度不同。它们的宽度分别为 18、21 和 25 个单位。
在公园翻新过程中,Monocarp 必须用新木板替换作为桥面的旧木板。
木板的标准长度为 60 个单位。Monocarp 已经知道每座桥需要 n n n 块木板。但由于桥的宽度不同,第一座桥需要长度为 18 的 n n n 块木板,第二座桥需要长度为 21 的 n n n 块木板,最后一座桥需要长度为 25 的 n n n 块木板。
负责翻修的工人可以将木板切割成若干部分,但拒绝将木板连接起来,因为这样会产生薄弱点,而且看起来很难看。
Monocarp 想买尽可能少的木板,但却苦于计算不出所需木板的数量。您能帮助他吗?
输入
第一行也是唯一一行包含一个整数
n
n
n
(
1
≤
n
≤
1000
)
(1 \leq n \leq 1000)
(1≤n≤1000)——三座桥所需的木板数量。
输出
打印一个整数——如果木板可以切割成若干部分,则 Monocarp 覆盖所有三座桥所需的最小标准长度木板数量(60 单位)。
示例
输入
1
输出
2
输入
3
输出
4
输入
1000
输出
1167
注意
在第一个示例中,可以将一块长度为 60 的木板切割成三块长度为 25、18 和 17 的木板,再将另一块长度为 60 的木板切割成两块长度为 39 和 21 的木板。这样,Monocarp 将会得到所有所需的木板。
解题思路
观察发现第一种和第二种的木板可以任意三三组合,剩下的和第三种只能任意两两组合,因此答案为 ⌊ 2 n 3 ⌋ + ⌈ 2 n % 3 + n 2 ⌉ \lfloor \frac{2n}{3} \rfloor +\lceil \frac{2n\%3+n}{2} \rceil ⌊32n⌋+⌈22n%3+n⌉
实现代码
void solve()
{
int n;
cin >> n;
cout << (2 * n) / 3 + (2 * n % 3 + n + 1) / 2 << "\n";
}
N. Fixing the Expression
表达式是由三个字符组成的字符串,其中第一个和最后一个字符是数字(从 0 到 9),中间的字符是比较符号(<, = 或 >)。
如果比较符号与数字匹配,则表达式为真(例如,如果第一位数字严格小于最后一位数字,则比较符号应为 <)。
例如,表达式 1<3、4>2、0=0 为真,而 5>5、7<3 不是真。
给你一个字符串 s s s,这是一个表达式。请尽可能少地更改字符,使 s s s 成为一个真表达式。请注意,如果 s s s 已经为真,则应保持原样。
输入
第一行包含一个整数
t
t
t
(
1
≤
t
≤
300
)
(1 \leq t \leq 300)
(1≤t≤300) - 测试用例数。
每个测试用例包含一行字符串 s s s ( ∣ s ∣ = 3 (|s| = 3 (∣s∣=3, s s s 的第一个和最后一个字符为数字,第二个字符为比较符号)。
输出
对于每个测试用例,打印一个由 3 个字符组成的字符串,即通过更改
s
s
s 中尽可能少的字符而得到的真表达式。如果有多个答案,则打印其中任何一个。
示例
输入
5
3<7
3>7
8=9
0=0
5<3
输出
3<7
8>7
8<9
0=0
0<3
解题思路
签到题,根据两边值的关系去修改符号即可。
实现代码
void solve()
{
string s;
cin >> s;
int a = s[0];
int b = s[2];
if (a < b)
s[1] = '<';
else if (a == b)
s[1] = '=';
else
s[1] = '>';
cout << s << "\n";
}
封面画师id:清风残影Sid
the Expression](https://codeforces.com/contest/2038/problem/N)
表达式是由三个字符组成的字符串,其中第一个和最后一个字符是数字(从 0 到 9),中间的字符是比较符号(<, = 或 >)。
如果比较符号与数字匹配,则表达式为真(例如,如果第一位数字严格小于最后一位数字,则比较符号应为 <)。
例如,表达式 1<3、4>2、0=0 为真,而 5>5、7<3 不是真。
给你一个字符串 s s s,这是一个表达式。请尽可能少地更改字符,使 s s s 成为一个真表达式。请注意,如果 s s s 已经为真,则应保持原样。
输入
第一行包含一个整数
t
t
t
(
1
≤
t
≤
300
)
(1 \leq t \leq 300)
(1≤t≤300) - 测试用例数。
每个测试用例包含一行字符串 s s s ( ∣ s ∣ = 3 (|s| = 3 (∣s∣=3, s s s 的第一个和最后一个字符为数字,第二个字符为比较符号)。
输出
对于每个测试用例,打印一个由 3 个字符组成的字符串,即通过更改
s
s
s 中尽可能少的字符而得到的真表达式。如果有多个答案,则打印其中任何一个。
示例
输入
5
3<7
3>7
8=9
0=0
5<3
输出
3<7
8>7
8<9
0=0
0<3
解题思路
签到题,根据两边值的关系去修改符号即可。
实现代码
void solve()
{
string s;
cin >> s;
int a = s[0];
int b = s[2];
if (a < b)
s[1] = '<';
else if (a == b)
s[1] = '=';
else
s[1] = '>';
cout << s << "\n";
}
封面画师id:清风残影Sid