Codeforces Round #741 (Div. 2) 题解

8 篇文章 0 订阅
8 篇文章 0 订阅
本文分享了四道算法竞赛题目及其解析,包括数论、位运算、数论与动态规划的应用。首先介绍了一道寻找最大模值的问题,通过分析模运算性质得出解决方案。接着是一道关于构建非素数的题目,通过枚举和质因数分解找出最少删除位数。第三题是二进制串处理,找到特定长度子串满足倍数关系。最后两题是关于序列和的处理,通过前缀和与位移技巧解决。每题均给出AC代码作为解答。
摘要由CSDN通过智能技术生成

旅行传送门

A. The Miracle and the Sleeper

题意:给定两个整数 l l l r r r :在所有整数对 ( a , b ) (a,b) (a,b) 中找到 a a a m o d mod mod b b b 的最大值,其中 l ≤ a ≤ b ≤ r l \leq a \leq b \leq r labr

题目分析:一开始写假了orz,考虑取模的性质,不难发现:如果 r 2 \frac{r}{2} 2r + 1 ≥ l 1 \geq l 1l ,此时答案为 r r r m o d mod mod ( r 2 + 1 ) (\frac{r}{2} + 1) (2r+1) ,否则答案为 r r r m o d mod mod l l l

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
const int maxn = 1e9 + 5;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int main(int argc, char const *argv[])
{
    int T = read();
    while (T--)
    {
        int a = read(), b = read();
        int tmp = b / 2 + 1;
        if (tmp < a)
            printf("%d\n", b % a);
        else
            printf("%d\n", b % tmp);
    }

    return 0;
}

B. Scenes From a Memory

题意:给你一个不含零的正整数 k k k ,问最多去掉多少位后,它才能变成一个非素数。

题目分析:比赛的时候猜了下最后删完剩下的位数应该不会大于 2 2 2 ,然后瞎搞了下过了XD

嗯,经典先写后证了,由于题目保证必有解,我们不妨考虑什么情况下只会剩一位数字呢?答案很明显,即 n n n 中包含数字 1 、 4 、 6 、 8 、 9 1、4、6、8、9 14689 ,因为这五个数均为非素数。

那如果没有上述五个数,这时就要考虑两位数了,因为 k k k 的位数很少,我们直接暴力就好,可以证明,两位数符合情况的有:

  • k k k 中存在两个相同的数(可被11整除)
  • k k k 中存在数字 2 2 2 5 5 5 ,且其不位于首位(即以 2 2 2 5 5 5 结尾的两位数)

如果上述情况都不符合,那么我们发现一个三位数的 k k k 必是 237 、 273 、 537 、 573 237、273、537、573 237273537573 之一,不难看出它们均可组成一个被 3 3 3 整除的两位数。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
using namespace std;

int vis[105], prime[105], cnt = 0;
void eulerSieve(int n)
{
    rep(i, 2, n)
    {
        if (!vis[i])
            prime[++cnt] = i;
        rep(j, 1, cnt)
        {
            if (i * prime[j] > n)
                break;
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}

int main(int argc, char const *argv[])
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    eulerSieve(100);
    int T;
    cin >> T;
    while (T--)
    {
        int n, ans = 0;
        cin >> n;
        string s, t;
        cin >> s;
        rep(i, 0, n - 1)
        {
            int c = s[i] - '0';
            if (c == 1 || c == 4 || c == 6 || c == 8 || c == 9)
                ans = c;
            if (ans)
                break;
        }
        if (ans)
        {
            printf("%d\n%d\n", 1, ans);
            continue;
        }
        rep(i, 0, n - 1)
        {
            rep(j, i + 1, n - 1)
            {
                t = s[i];
                t += s[j];
                if (vis[stoi(t)])
                    ans = stoi(t);
                if (ans)
                    break;
            }
            if (ans)
                break;
        }
        printf("%d\n%d\n", 2, ans);
    }
    return 0;
}

C. Rings

题意:给你一个长为 n n n 的二进制字符串,你可以从中截取不同区间且长度均 $ \geq \lfloor \frac{n}{2} \rfloor$ 的两段,但要保证这两段子串转换为十进制后成倍数关系,问应该截取哪两段?

题目分析:根据序列是否为非零序列,可以分为以下两种情况:

  • 若序列中大于 n 2 \frac{n}{2} 2n 的位置有 p o s pos pos 出现过 0 0 0 ,则截取的两段分别为 [ 1 , p o s ] [1,pos] [1,pos] [ 1 , p o s + 1 ] [1,pos+1] [1,pos+1] ,小于 n 2 \frac{n}{2} 2n 同理。

  • 若序列为非零序列(即全由 1 1 1 组成),则截取的两段分别为 [ 1 , ⌊ n 2 ⌋ ] [1,\lfloor \frac{n}{2} \rfloor] [1,2n] [ 1 , 2 × ⌊ n 2 ⌋ ] [1,2 \times \lfloor \frac{n}{2} \rfloor] [1,2×2n]

为什么这样取呢,因为若存在 0 0 0 ,我们可以利用位移一位相乘/相除 2 2 2 的关系构造一组解,若不存在 0 0 0 ,就截取 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n 长度的 1 1 1 2 × ⌊ n 2 ⌋ 2\times \lfloor \frac{n}{2} \rfloor 2×2n 长度的 1 1 1 ,比如有这样一个序列: 111111111 111111111 111111111 ,我们可以截取 4 4 4 1 1 1 8 8 8 1 1 1 8 8 8 1 1 1 可以看作是 4 4 4 1 1 1 左移四位再加上自己后得到,从而形成了倍数关系。

需要注意本题是向下取整,这点十分重要。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
using namespace std;

int main(int argc, char const *argv[])
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T, n;
    cin >> T;
    while (T--)
    {
        string s;
        cin >> n >> s;
        int flag = 0, pos = 0;
        rep(i, 0, n - 1) if (s[i] - '0' == 0)
        {
            flag = 1;
            pos = i;
            break;
        }
        if (flag)
            if (pos >= n / 2)
                printf("%d %d %d %d\n", 1, pos + 1, 1, pos);
            else
                printf("%d %d %d %d\n", pos + 1, n, pos + 2, n);
        else
            n <= 3 ? printf("%d %d %d %d\n", 1, n, 1, 1) : printf("%d %d %d %d\n", 1, n / 2 * 2, 1, n / 2);
    }
    return 0;
}

D1. Two Hundred Twenty One (easy version)

题意:给你一个由 + + + − - 组成的字符串 s s s s [ i ] s[i] s[i] + + + 代表 a i a_i ai 值为 1 1 1 s [ i ] s[i] s[i] − - 代表 a i a_i ai − 1 -1 1 ,每次询问一个区间 [ l , r ] [l,r] [l,r] ,问最少移除序列中多少个字符后,可以使得 a l + a l + 1 + . . . + a r − 1 + a r a_l + a_{l+1} + ... + a_{r-1} + a_r al+al+1+...+ar1+ar 之和,即 ∑ i = l r a i \sum_{i=l}^ra_i i=lrai 0 0 0

题目分析:这道题的分析用到了 d p dp dp 的思想,对于任意一段序列,不难发现最终答案只与它的区间和有关,与长度无关,对于奇数和的情况,我们一定能找到一个位置 i i i ,在保证 a i a_i ai 有贡献的情况下,使得 [ l , i ] [l,i] [l,i] [ i + 1 , r ] [i+1,r] [i+1,r] 的值相差为1,这时候删去位置 i i i 的值,就能保证 i i i 前后序列之和大小相等、符号相反,此时的 c o s t cost cost 1 1 1 ,当情况为偶数和的时候,其删去头尾之一即退化为奇数和,所以总 c o s t cost cost 2 2 2

什么叫有贡献呢?比如 + - + - (+ +) + - + 虽然 s u m [ 4 ] sum[4] sum[4] s u m [ 6 ] sum[6] sum[6] 的前缀和相等,但中间括号括起来的两个 + 就属于没有贡献的值,因为这对 + 相互抵消了,所以我们要找的位置 i i i = 4 4 4

其实再举个通俗点的例子:比如你区间符号和是 9 9 9 ,然后你找一个区间符号和为 5 5 5 的位置, 5 5 5 这一个位置就是因为存在 a i a_i ai 这个元素,使得原来是 4 4 4 的现在变成 5 5 5 了,它很碍事,所以我们拿掉它,那么之后的 4 4 4 个贡献现在被取反了,正好和前面相互抵消了 。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
using namespace std;
const int maxn = 3e5 + 5;

string s;
int t, n, q, sum[maxn]; //sum为前缀和

int main(int argc, char const *argv[])
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n >> q >> s;
        rep(i, 1, n) sum[i] = sum[i - 1] + (i & 1 ? (s[i - 1] == '+' ? 1 : -1) : (s[i - 1] == '+' ? -1 : 1));
        int l, r;
        rep(i, 1, q)
        {
            cin >> l >> r;
            int res = sum[r] - sum[l - 1];
            puts(res ? (res & 1 ? "1" : "2") : "0");
        }
    }
    return 0;
}

D2. Two Hundred Twenty One (hard version)

题目分析:其实如果D1想明白了,D2就很简单了,根据之前的理论,我们只需要分析奇数和的情况就好了,开个数组记录下每个值的位置 p o s pos pos ,设 r e s res res = s u m [ r ] − s u m [ l − 1 ] sum[r] - sum[l-1] sum[r]sum[l1] ,每次询问偶数和先化奇数和,然后奇数和就找询问区间内前缀和为 r e s 2 + 1 \frac{res}{2} + 1 2res+1 出现的第一个位置就好了。

需要注意的是前缀和可能会出现负数的情况,因此我们不妨给所有的前缀和加上 n n n 后再进行操作。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
using namespace std;
const int maxn = 3e5 + 5;

string s;
int t, n, q, sum[maxn];

int main(int argc, char const *argv[])
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n >> q >> s;
        vector<vector<int>> pos(2 * n + 1);
        //处理负数
        pos[n].push_back(0);
        sum[0] = n;
        rep(i, 1, n) sum[i] = sum[i - 1] + (i & 1 ? (s[i - 1] == '+' ? 1 : -1) : (s[i - 1] == '+' ? -1 : 1)), pos[sum[i]].push_back(i);
        int l, r, ans;
        rep(i, 1, q)
        {
            cin >> l >> r;
            int res = sum[r] - sum[l - 1];
            if (!res)
                cout << "0" << endl;
            else if (abs(res) & 1)
            {
                cout << "1" << endl;
                //根据区间和的正负性进行不同操作,不要漏掉之前的前缀和sum[l - 1]
                //6 / 2 + 1 = 4, -6 / 2 - 1 = -4
                int tmp = sum[l - 1] + (res > 0 ? res / 2 + 1 : res / 2 - 1);
                cout << *lower_bound(pos[tmp].begin(), pos[tmp].end(), l) << endl;
            }
            else
            {
                cout << "2" << endl;
                --r; //化为奇数和的情况
                res = sum[r] - sum[l - 1];
                int tmp = sum[l - 1] + (res > 0 ? res / 2 + 1 : res / 2 - 1);
                cout << *lower_bound(pos[tmp].begin(), pos[tmp].end(), l) << " " << r + 1 << endl;
            }
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值