Codeforces Round #741 (Div. 2) 題解


旅行傳送門

A. The Miracle and the Sleeper

題意:給定兩個整數 \(l\)\(r\) :在所有整數對 \((a,b)\) 中找到 \(a\) \(mod\) \(b\) 的最大值,其中 \(l \leq a \leq b \leq r\)

題目分析:一開始寫假了orz,考慮取模的性質,不難發現:如果 $ \frac{r}{2} $ + \(1 \geq l\) ,此時答案為 \(r\) \(mod\) $(\frac{r}{2} $ + \(1)\) ,否則答案為 \(r\) \(mod\) \(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\) ,問最多去掉多少位后,它才能變成一個非素數。

題目分析:比賽的時候猜了下最后刪完剩下的位數應該不會大於 \(2\) ,然后瞎搞了下過了XD

嗯,經典先寫后證了,由於題目保證必有解,我們不妨考慮什么情況下只會剩一位數字呢?答案很明顯,即 \(n\) 中包含數字 \(1、4、6、8、9\) ,因為這五個數均為非素數。

那如果沒有上述五個數,這時就要考慮兩位數了,因為 \(k\) 的位數很少,我們直接暴力就好,可以證明,兩位數符合情況的有:

  • \(k\) 中存在兩個相同的數(可被11整除)
  • \(k\) 中存在數字 \(2\)\(5\) ,且其不位於首位(即以 \(2\)\(5\) 結尾的兩位數)

如果上述情況都不符合,那么我們發現一個三位數的 \(k\) 必是 \(237、273、537、573\) 之一,不難看出它們均可組成一個被 \(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\) 的二進制字符串,你可以從中截取不同區間且長度均 $ \geq \lfloor \frac{n}{2} \rfloor$ 的兩段,但要保證這兩段子串轉換為十進制后成倍數關系,問應該截取哪兩段?

題目分析:根據序列是否為非零序列,可以分為以下兩種情況:

  • 若序列中大於 \(\frac{n}{2}\) 的位置有 \(pos\) 出現過 \(0\) ,則截取的兩段分別為 \([1,pos]\)\([1,pos+1]\) ,小於 \(\frac{n}{2}\) 同理。

  • 若序列為非零序列(即全由 \(1\) 組成),則截取的兩段分別為 \([1,\lfloor \frac{n}{2} \rfloor]\)\([1,2 \times \lfloor \frac{n}{2} \rfloor]\)

為什么這樣取呢,因為若存在 \(0\) ,我們可以利用位移一位相乘/相除 \(2\) 的關系構造一組解,若不存在 \(0\) ,就截取 \(\lfloor \frac{n}{2} \rfloor\) 長度的 \(1\) 與 $ 2 \times \lfloor \frac{n}{2} \rfloor$ 長度的 \(1\) ,比如有這樣一個序列:\(111111111\) ,我們可以截取 \(4\)\(1\)\(8\)\(1\)\(8\)\(1\) 可以看作是 \(4\)\(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[i]\)\(+\) 代表 \(a_i\) 值為 \(1\)\(s[i]\)\(-\) 代表 \(a_i\)\(-1\) ,每次詢問一個區間 \([l,r]\) ,問最少移除序列中多少個字符后,可以使得 \(a_l + a_{l+1} + ... + a_{r-1} + a_r\) 之和,即 \(\sum_{i=l}^ra_i\)\(0\)

題目分析:這道題的分析用到了 \(dp\) 的思想,對於任意一段序列,不難發現最終答案只與它的區間和有關,與長度無關,對於奇數和的情況,我們一定能找到一個位置 \(i\) ,在保證 \(a_i\) 有貢獻的情況下,使得 \([l,i]\)\([i+1,r]\) 的值相差為1,這時候刪去位置 \(i\) 的值,就能保證 \(i\) 前后序列之和大小相等、符號相反,此時的 \(cost\)\(1\) ,當情況為偶數和的時候,其刪去頭尾之一即退化為奇數和,所以總 \(cost\)\(2\)

什么叫有貢獻呢?比如 + - + - (+ +) + - + 雖然 \(sum[4]\)\(sum[6]\) 的前綴和相等,但中間括號括起來的兩個 + 就屬於沒有貢獻的值,因為這對 + 相互抵消了,所以我們要找的位置 \(i\) = \(4\)

其實再舉個通俗點的例子:比如你區間符號和是 \(9\) ,然后你找一個區間符號和為 \(5\) 的位置, \(5\) 這一個位置就是因為存在 \(a_i\) 這個元素,使得原來是 \(4\) 的現在變成 \(5\) 了,它很礙事,所以我們拿掉它,那么之后的 \(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就很簡單了,根據之前的理論,我們只需要分析奇數和的情況就好了,開個數組記錄下每個值的位置 \(pos\) ,設 \(res\) = \(sum[r] - sum[l-1]\) ,每次詢問偶數和先化奇數和,然后奇數和就找詢問區間內前綴和為 $ \frac{res}{2} + 1$ 出現的第一個位置就好了。

需要注意的是前綴和可能會出現負數的情況,因此我們不妨給所有的前綴和加上 \(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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM