Codeforces Round 930(Div.1)&(Div.2) 2A~2D

编程竞赛题目解析:数组操作、路径优化与逻辑谜题,

2A.Shuffle Party(枚举)

题意:

给你一个数组a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,,an。最初,每个1≤i≤n1\le i\le n1in都有ai=ia_i=iai=i

整数k≥2k\ge2k2的运算swap(k)\texttt{swap}(k)swap(k)定义如下:

  • ddd是不等于kkkkkk的最大除数†^\dagger。然后交换元素ada_dadaka_kak

假设你按照这样的顺序对每一个i=2,3,…,ni=2,3,\ldots,ni=2,3,,n进行swap(i)\texttt{swap}(i)swap(i)。找出111在数组中的位置。换句话说,在执行这些操作后,找出jjjaj=1a_j=1aj=1的位置。

†^\dagger 如果存在一个整数zzz使得y=x⋅zy=x\cdot zy=xzyyy的整除数,那么整数xxx就是yyy的整除数。

分析:

关注111的位置可以发现规律。111可以换到位置222,位置222只能换到位置444,位置444换到位置888,以此类推发现换到的位置都是222的幂次,枚举一下最大的幂次即可。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 30; i >= 0; i--) {
            if (n >= (1 << i)) {
                cout << (1 << i) << endl;
                break;
            }
        }
    }
    return 0;
}

2B.Binary Path(模拟)

题意:

给你一个2×n2\times n2×n的网格,网格中充满了"0"和"1"。假设第iii行和第jjj列的交叉点上的数字是ai,ja_{i,j}ai,j

在左上角的(1,1)(1,1)(1,1)网格中有一只蚱蜢,它只能向右或向下跳一格。它想到达右下方的(2,n)(2,n)(2,n)网格。考虑一个长度为n+1n+1n+1的二进制字符串,它由路径单元格中的数字按经过的顺序组成。

题目目标是

  1. 通过选择任意一条可用路径,找出字典序最小的†^\dagger字符串;
  2. 找出能得到这个字典序最小字符串的路径数。

†^\dagger如果两个字符串sssttt的长度相同,那么当且仅当在sssttt不同的第一个位置上,字符串sss的元素小于ttt中的相应元素时,sss在词法上小于ttt

分析:

由于每一次只能往下和往右,所以其实我们只需要找到一个特殊点,从这个点往下走即可。如果右侧字符小于下面字符,一定向右走,这个位置记为xxx。如果右侧字符大于下面字符,此时一定得向下走,这个位置记为yyy

我们在[x+1,y][x+1,y][x+1,y]之间随时都可以向下走,这样得到的都是最小字典序字符串。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        string a, b;
        cin >> a >> b;
        a = ' ' + a;
        b = ' ' + b;
        string ans;
        int y = n;
        int x = 0;
        for (int i = 1; i < n; i++) {
            if (b[i] < a[i + 1]) {
                y = i;
                break;
            }
            if (b[i] > a[i + 1]) {
                x = i;
            }
        }
        if (y == n && x == 0) {
            ans = a + b.back();
            cout << ans << endl;
            cout << n << endl;
        } else {
            ans = a.substr(1, y) + b.substr(y);
            cout << ans << endl;
            if (x == 0) {
                cout << y << endl;
            } else {
                cout << y - x << endl;
            }
        }
    }
    return 0;
}

2C(1A).Bitwise Operation Wizard(数学)

题意:

本题是一个互动问题。

有一个秘密序列p0,p1,…,pn−1p_0,p_1,\ldots,p_{n-1}p0,p1,,pn1,它是{0,1,…,n−1}\{0,1,\ldots,n-1\}{0,1,,n1}的排列组合。

您需要找到任意两个索引iiijjj使pi⊕pjp_i\oplus p_jpipj最大化。

为此,可以提出查询。每个查询的形式如下:可以选择任意的索引aaabbbcccddd(0≤a,b,c,d<n0\le a,b,c,d\lt n0a,b,c,d<n)。(0≤a,b,c,d<n0\le a,b,c,d \lt n0a,b,c,d<n)。接下来会计算x=(pa∣pb)x=(p_a\mid p_b)x=(papb)y=(pc∣pd)y=(p_c\mid p_d)y=(pcpd)。最后,你会得到xxxyyy的比较结果。换句话说,你会被告知是x<yx\lt yx<yx>yx\gt yx>y还是x=yx=yx=y

请找出任意两个索引iiijjj(0≤i,j<n0\le i,j\lt n0i,j<n),使得pi⊕pjp_i\oplus p_jpipj在所有这样的索引对中最大,最多可以有3n3n3n个查询。如果有多对索引满足条件,输出其中任何一个。

分析:

这道题考虑从特殊的地方入手,题目中给的数组是一个从000n−1n-1n1的排列,那么最大的数显然是n−1n-1n1,然后要找出n−1n-1n1000的哪些位置为111的数,显然两者进行∣\mid运算的值应该最大,但是如果仅仅是找或运算的话,那么n−1n-1n1111的位置,且找到的数对应位置也为111的位置的那种值需要排除,显然我们的目标值中111的个数最少,目标值为111的位置所有找到的值对应的位置应该也是111,但是应该比目标值大。所以就要找到所有值中最小的一个。比较两个数的大小可以通过询问a,a,b,ba,a,b,ba,a,b,b来得到。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        if (n == 2) {
            cout << "! 0 1" << endl;
        } else {
            char op[2];
            int mx = 0, mi = 0;
            for (int i = 1; i < n; i++) {
                cout << "? " << mx << " " << mx << " " << i << " " << i << endl;
                cin >> op;
                if (op[0] == '<')
                    mx = i;
            }
            for (int i = 1; i < n; i++) {
                cout << "? " << mx << " " << mi << " " << mx << " " << i << endl;
                cin >> op;
                if (op[0] == '<')
                    mi = i;
                else if (op[0] == '=') {
                    cout << "? " << mi << " " << mi << " " << i << " " << i << endl;
                    cin >> op;
                    if (op[0] == '>')
                        mi = i;
                }
            }
            cout << "! " << mx << " " << mi << endl;
        }
    }
    return 0;
}

2D(1B).Pinball(思维)

题意:

有一个长度为nnn的一维网格。网格的第iii个单元格包含一个字符sis_isi,该字符可以是’<‘或者’>'。

当一个弹球被放置在其中一个单元格上时,它会按照以下规则移动:

  • 如果弹球位于第iii个单元格上,且sis_isi为’<‘, 弹球下一秒向左移动一个单元格。如果sis_isi 是’>',则向右移动一个单元格。
  • 弹球移动后,字符sis_isi将被反转(即如果sis_isi以前是’<‘,将变为’>',反之亦然)。
  • 当弹球离开网格时,它将停止移动:无论是从左边界还是从右边界。

本题需要回答nnn独立查询。在第iii次查询中,弹球将被放置在第iii个单元格中。请注意,我们总是在初始网格上放置一个弹球。

对于每个查询,计算弹球离开网格需要多少秒。可以证明,弹球总是会在有限步数内离开网格。

分析:

会改变最初放置在位置ppp的弹球的方向的单元格是ppp左边的’>‘和ppp右边的’<'。

为方便起见,假设sps_psp为’>'、k=min(countright(1,p),countleft(p+1,n))k=min(countright(1,p),countleft(p+1,n))k=min(countright(1,p),countleft(p+1,n)),弹球从左边界离开(其他情况也可以类似方法处理)。

可以通过前缀相加+二进制搜索得到right[1,…,k]right[1,\ldots,k]right[1,,k]left[1,…,k]left[1,\ldots,k]left[1,,k],其中rightrightright表示ppp左边的’>‘的单元格序列(按递减顺序),leftleftleft表示ppp右边的’<'的单元格序列(按递增顺序)。

rightrightrightleftleftleft来描述弹球的轨迹:

  • 第一段:弹球从right1right_1right1移动到left1left_1left1

  • 第二段:弹球从left1left_1left1移动到right2right_2right2

  • 第三段:弹球从right2right_2right2移动到left3left_3left3

  • …\ldots

  • 2k2k2k段:弹球从leftkleft_kleftk移动到左边界。

不难发现,可以使用前缀和来存储序列之和,然后快速计算弹球移动的时间。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 1000010;
int T, n;
ll Sl[N], Sr[N], IDl[N], IDr[N];
char s[N];

int findpre(int x) {
    int L = 0, R = n + 1, M;
    while (L + 1 != R) {
        M = (L + R) >> 1;
        if (Sr[M] < x)
            L = M;
        else
            R = M;
    }
    return R;
}

int findsuf(int x) {
    int L = 0, R = n + 1, M;
    while (L + 1 != R) {
        M = (L + R) >> 1;
        if (Sl[n] - Sl[M - 1] < x) R = M;
        else L = M;
    }
    return L;
}

int main() {
    scanf_s("%d", &T);
    while (T--) {
        scanf_s("%d %s", &n, s);
        for (int i = 1; i <= n; i++) {
            Sr[i] = Sr[i - 1] + (s[i - 1] == '>');
            Sl[i] = Sl[i - 1] + (s[i - 1] == '<');
            IDr[i] = IDr[i - 1] + i * (s[i - 1] == '>');
            IDl[i] = IDl[i - 1] + i * (s[i - 1] == '<');
        }
        for (int i = 1; i <= n; i++) {
            if (s[i - 1] == '>') {
                if (Sr[i] > Sl[n] - Sl[i]) {
                    int p = findpre(Sr[i] - (Sl[n] - Sl[i]));
                    printf_s("%lld ", 2 * ((IDl[n] - IDl[i]) - (IDr[i] - IDr[p - 1])) + i + (n + 1));
                } else {
                    int p = findsuf((Sl[n] - Sl[i]) - Sr[i] + 1);
                    printf_s("%lld ", 2 * ((IDl[p] - IDl[i]) - (IDr[i] - IDr[0])) + i);
                }
            } else {
                if (Sr[i] >= Sl[n] - Sl[i - 1]) {
                    int p = findpre(Sr[i] - (Sl[n] - Sl[i - 1]) + 1);
                    printf_s("%lld ", 2 * ((IDl[n] - IDl[i - 1]) - (IDr[i] - IDr[p - 1])) - i + (n + 1));
                } else {
                    int p = findsuf((Sl[n] - Sl[i - 1]) - Sr[i]);
                    printf_s("%lld ", 2 * ((IDl[p] - IDl[i - 1]) - (IDr[i] - IDr[0])) - i);
                }
            }
        }
        printf("\n");
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值