2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest 个人题解(A,B,C,G,J,K,L,N)

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=aicbi。如果不支付奖金,工程师将不会自愿完成任何工作。

工程师们在一起工作多年,因此他们知道奖金将如何分配以及同事们对劳动的重视程度。也就是说,团队中的每个工程师都知道所有的 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 1n1000 ; 1 ≤ k ≤ 1 0 6 1 \leq k \leq 10^6 1k106 )——分别是公司的工程师人数和项目所需的工作单位数量。

第二行包含 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 1ai109 ),其中 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 1bi1000 ),其中 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 0cik )——在每个工程师都表现最优的情况下,每个工程师完成的工作量。请注意,答案是唯一的。

示例
输入

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 1t104 )——测试用例数。

每个测试用例的第一行包含一个整数 n n n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \leq n \leq 2 \cdot 10^5 2n2105 )。

每个测试用例的第二行包含 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 1ai109 )。

输入的附加限制:所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105

输出
对于每个测试用例,打印一个整数——你必须执行的最小操作数。如果不可能使数组中的所有元素都相等,则打印 -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,x1] 相等的数组。

因此,最后的相等元素 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 aix 肯定是不操作;当 a i > x a_i>x ai>x 时,如果 a i − x a_i-x aix 为奇数,就先对 a i − 1 a_{i-1} ai1 操作一次,让其变成偶数,然后对其操作 a i − x 2 \frac{a_{i}-x}{2} 2aix 次。

如果只操作一遍,也许任然存在 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 1t25000 )——测试用例数。

每个测试用例的第一行包含一个整数 n n n ( 8 ≤ n ≤ 2 ⋅ 1 0 5 8 \leq n \leq 2 \cdot 10^5 8n2105 )。

每个测试用例的第二行包含 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 109ai109 )。

输入的附加限制:所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105

输出
对于每个测试用例,打印答案如下:

如果不可能构造出符合语句限制条件的矩形,则打印包含 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

您可以进行以下查询:

  1. 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 1t1000 )——测试用例数。

在每个测试用例开始时,陪审团程序发送一个整数 n n n ( 2 ≤ n ≤ 50 2 \leq n \leq 50 2n50 )——字符串的长度。

之后,您的程序可以通过打印以下一行向陪审团程序提交查询(打印完一行后不要忘记刷新输出!):

1 t t t 表示询问 " s s s 中的连续子串 t t t 出现了多少次?"
对于每个查询,陪审团都会在单独一行中打印一个整数。它要么是查询的答案,如果查询是正确的,并且没有超出查询限制;或者是整数 −1 ,如果您的查询不正确(例如,未满足约束 1 ≤ ∣ t ∣ ≤ n 1 \leq |t| \leq n 1tn 或字符串 t t t 包含 0 和 1 以外的字符),或者您在处理当前测试用例时提出了太多查询。

要提交答案,您的程序应按以下格式发送一行(打印完一行后不要忘记刷新输出!):

0 i i i c c c,其中 1 ≤ i ≤ n 1 \leq i \leq n 1in 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) (1n103)

然后是 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) (1bi106) - 一辆有 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) (1pi106) - 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) (2n106;1a,b106)

输出
打印一个整数——从 ( 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=nmaxi r m = n − maxj rm=n-\text{maxj} rm=nmaxj ,可以发现 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[i1][j],dp[i][j1])+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) (1n1000)——三座桥所需的木板数量。

输出
打印一个整数——如果木板可以切割成若干部分,则 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) (1t300) - 测试用例数。

每个测试用例包含一行字符串 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) (1t300) - 测试用例数。

每个测试用例包含一行字符串 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值