Codeforces Round #752 (Div. 2)

本文详细解析了Codeforces Round #752 (Div. 2)中的五道编程题目,包括A.Era、B.XOR Specia-LIS-t、C.Di-visible Confusion、D.Moderate Modular Mode和E.Extreme Extension。通过对题目描述、问题分析及代码实现的阐述,帮助参赛者理解解题思路和优化方法。

A.Era

题目描述

​ 给一个长为 nnn 的序列 a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,,an,你可以对序列做任意次如下所述的操作:选择一个数字 kkk(每次操作选择的数字可以不同),将其插入到序列 aaa 的任意位置,比如:a=[3,3,4]a=[3,3,4]a=[3,3,4]k=2k=2k=2,操作后可能的序列有 [2,3,3,4],[3,2,3,4],[3,3,2,4],[3,3,4,2][2,3,3,4],[3,2,3,4],[3,3,2,4],[3,3,4,2][2,3,3,4],[3,2,3,4],[3,3,2,4],[3,3,4,2]

​ 操作尽可能少的次数,使序列 aaa 满足:∀i,ai≤i\forall i,a_i\leq ii,aii,求这个最少的操作次数。

​ 数据范围:多组测试数据,1≤n≤1001\leq n\leq 1001n1001≤ai≤1091\leq a_i\leq 10^91ai109

分析

​ 对于某个下标 iii,如果 ai>ia_i>iai>i,最少需要在位置 iii 前插入 ai−ia_i-iaii 个数字。枚举所有的 ai−ia_i-iaii,其中的最大值即为答案,即 ans=max⁡i=1n{ai}ans=\max\limits_{i=1}^{n}\{a_i\}ans=i=1maxn{ai}

代码

#include<bits/stdc++.h>

using namespace std;

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            int k;
            scanf("%d", &k);
            ans = max(ans, k - i);
        }
        cout << ans << endl;
    }
}

B.XOR Specia-LIS-t

题目描述

​ 给一个长为 nnn 的序列 a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,,an,把它分成若干个不相交的子序列,第 iii 个子序列的最长递增子序列的长度定义为 hih_ihi。比如我们把序列 [2,5,3,1,4,3,2,2,5,1][2,5,3,1,4,3,2,2,5,1][2,5,3,1,4,3,2,2,5,1] 分割成 [2,5,3,1,4],[3,2,2,5],[1][2,5,3,1,4],[3,2,2,5],[1][2,5,3,1,4],[3,2,2,5],[1],则 h=[3,2,1]h=[3,2,1]h=[3,2,1]

​ 判断能否以某种方式分割 aaa 序列,使得 hhh 序列的异或和等于 000

​ 数据范围:多组测试数据,2≤n≤1052\leq n\leq 10^52n1051≤ai≤1091\leq a_i\leq 10^91ai109nnn 的总和不超过 3×1053\times 10^53×105

分析

​ 如果 nnn 是偶数,把 aaa 序列分割成长为 111nnn 个子序列,答案一定是 YES

​ 如果 nnn 是奇数,我们考虑寻找满足 ai≥ai+1a_i\geq a_{i+1}aiai+1 的相邻的两个数字,把这两个数字作为一个子序列分割出来,最长递增子序列的长度为 111,如果找的到,答案为 YES,否则为 NO

代码

#include<bits/stdc++.h>

using namespace std;
int a[300010];

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        bool flag = true;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        for (int i = 1; i <= n - 1; i++)
            if (a[i] >= a[i + 1]) {
                flag = false;
            }
        if (n % 2 == 0 || flag == false) {
            puts("YES");
        } else {
            puts("NO");
        }
    }
    return 0;
}

C.Di-visible Confusion

题目描述

​ 给一个长为 nnn 的序列 a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,,an, 进行如下操作:选择一个 aia_iai,要求 (i+1)∤ai(i+1)\nmid a_i(i+1)ai,将其从序列中移除。判断能否通过若干次操作使得序列变为空。

​ 数据范围:多组测试数据,1≤n≤1051\leq n\leq 10^51n1051≤ai≤1091\leq a_i\leq 10^91ai109nnn 的总和不超过 3×1053\times 10^53×105

分析

​ 对于所有的 iii,如果 222 ~ i+1i+1i+1 中至少存在一个数字不整除 aia_iai,则答案为 YES,否则答案为 NO

​ 使用数学归纳法来证明:如果 ana_nan 前面的 n−1n-1n1 个数字都能被移除,则 ana_nan 也能在其位置为 $1 $ ~ nnn 中的某个位置时被移除(假设该位置为 kkkana_nan 可以在序列中有 k−1k-1k1 个数字时被移除)。

​ 下面考虑如何快速判断 222 ~ $ i+1$ 中是否至少有一个数字不整除 aia_iai。如果 aia_iai 能被 222 ~ $ i+1$ 的所有数字整除,这代表 aia_iai 也能被 LCM(2,3,⋯ ,i+1)\text{LCM}(2,3,\cdots,i+1)LCM(2,3,,i+1) 整除,但是当 n=22n=22n=22 时,LCM(2,3,⋯ ,23)>109>ai\text{LCM}(2,3,\cdots,23)>10^9>a_iLCM(2,3,,23)>109>ai。所以当 i≥22i\geq 22i22 时,必然存在一个数字不整除 aia_iai,不需要逐一判断;当 i<22i<22i<22 时,暴力检验。时间复杂度 O(n+212)O(n+21^2)O(n+212)

代码

#include<bits/stdc++.h>

using namespace std;

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        bool flag = true;
        for (int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            bool f = false;
            for (int j = i + 1; j >= 2; j--) {
                if (x % j != 0) {
                    f = true;
                    break;
                }
            }
            flag = flag & f;
        }
        if (flag == true) {
            puts("YES");
        } else {
            puts("NO");
        }
    }
    return 0;
}

D.Moderate Modular Mode

题目描述

​ 有两个偶数 x,y(2≤x,y≤109)x,y(2\leq x,y\leq 10^9)x,y(2x,y109),找到一个数字 n(2≤n≤1018)n(2\leq n\leq 10^{18})n(2n1018),使得 nmod  x=ymod  nn\mod x=y\mod nnmodx=ymodn

分析

​ 如果 x>yx>yx>y,则 (x+y)mod  x=ymod  x=ymod  (x+y)=y(x+y)\mod x=y\mod x=y\mod (x+y)=y(x+y)modx=ymodx=ymod(x+y)=y,即 n=x+yn=x+yn=x+y

​ 如果 x≤yx\leq yxy

​ 结论一:n≥xn\geq xnx

​ 证明:使用反证法证明,存在某个 n<xn<xn<x 使得 nmod  x=ymod  nn\mod x=y\mod nnmodx=ymodn 成立。则 nmod  x=nn\mod x =nnmodx=n,但 ymod  n<ny\mod n<nymodn<n,与假设矛盾,因此结论一正确。

​ 结论二:n≤yn\leq yny

​ 证明:使用反证法证明,存在某个 n>yn>yn>y 使得 nmod  x=ymod  nn\mod x=y\mod nnmodx=ymodn 成立。则 nmod  x<xn\mod x<xnmodx<x,但 ymod  n=y≥xy\mod n=y\geq xymodn=yx,与假设矛盾, 因此结论二正确。

​ 所以 x≤n≤yx \leq n \leq yxny,下面考虑如何求出 nnn 确切的值。

​ 假设有一个 XXX 轴,一开始在 000 点,从 000 点跳到 yyy 点,每次跳跃的距离为 xxx。有可能在最后一次跳跃后跳过点 yyy,设最后一次跳跃前的位置为 p=y−ymod  xp=y-y\mod xp=yymodx,从位置 ppp 跳跃到 yyy 点需要两个步骤。因为 y−py-pyp 是偶数,所以我们需要跳跃 y−p2\frac{y-p}{2}2yp 个单位,此时跳到了位置 t=p+y−p2t=p+\frac{y-p}{2}t=p+2yp,可以发现 ttt 就是我们要求的 nnn,因为 tmod  x=y−p2t\mod x=\frac{y-p}{2}tmodx=2ypymod  t=(y−p)−y−p2=y−p2y\mod t=(y-p)-\frac{y-p}{2}=\frac{y-p}{2}ymodt=(yp)2yp=2yp,即 n=t=y−ymod  x2n=t=y-\frac{y\mod x}{2}n=t=y2ymodx

代码

#include<bits/stdc++.h>

using namespace std;

int main() {
    int T;
    cin >> T;
    while (T--) {
        int x, y;
        cin >> x >> y;
        if (x <= y) {
            cout << y - y % x / 2 << endl;
        } else {
            cout << x + y << endl;
        }
    }
    return 0;
}

E.Extreme Extension

题目描述

​ 给一个长为 n(1≤n≤105)n(1\leq n\leq 10^5)n(1n105) 的序列 a1,a2,⋯ ,an(1≤ai≤105)a_1,a_2,\cdots,a_n(1\leq a_i\leq 10^5)a1,a2,,an(1ai105),每次操作可以选择序列中的一个数字 aia_iai,将其拆分为两个数字之和并代替 aia_iai 加入到序列中。定义一个序列的 极值 为使得数组单调不下降所需的最少操作次数,求序列 aaa 的所有子序列的 极值 之和,答案对 998244353998244353998244353 取模。

分析

​ 对于 ai>ai+1a_i>a_{i+1}ai>ai+1 的情况,需要将 aia_iai 拆成 kkk 个数字:1≤b1≤b2≤⋯≤bk≤ai+11\leq b_1\leq b_2\leq \cdots\leq b_k\leq a_{i+1}1b1b2bkai+1b1+b2+⋯+bk=aib_1+b_2+\cdots+b_k=a_ib1+b2++bk=ai。由于 bk≤ai+1b_k\leq a_{i+1}bkai+1,所以 k≥⌈aiai+1⌉k\geq \lceil\frac{a_i}{a_{i+1}}\rceilkai+1ai 。显然 b1b_1b1 越大操作的次数就越少(比如 [4,4,4,5][4,4,4,5][4,4,4,5][3,3,3,3,5][3,3,3,3,5][3,3,3,3,5] 要好),所以令 k=⌈aiai+1⌉k=\lceil\frac{a_i}{a_{i+1}}\rceilk=ai+1ai。注意到 b1≤⌊aik⌋b_1\leq \lfloor\frac{a_i}{k}\rfloorb1kai,令 b1=⌊aik⌋=⌊ai⌈aiai+1⌉⌋b_1=\lfloor\frac{a_i}{k}\rfloor=\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{a_{i+1}}\big\rceil}\Bigg\rfloorb1=kai=ai+1aiai。经过 k−1k-1k1 次操作即可将 aia_iai 拆分成 [b1,b2,⋯ ,bk][b_1,b_2,\cdots,b_k][b1,b2,,bk]

​ 按照如下步骤求出 极值

  • 倒序遍历 i=n−1i=n-1i=n1 ~ 111
  • 答案加 ⌈aiai+1⌉−1\lceil\frac{a_i}{a_{i+1}}\rceil-1ai+1ai1
  • ai=⌊ai⌈aiai+1⌉⌋​a_i=\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{a_{i+1}}\big\rceil}\Bigg\rfloor​ai=ai+1aiai

时间复杂度为 O(n)O(n)O(n),求出所有子序列的 极值 之和的时间复杂度为 O(n2)O(n^2)O(n2),考虑如何用更优地时间复杂度解决此题。

dp(i,x)dp(i,x)dp(i,x) 为第 iii 个数被拆分为最小元素为 xxx 的若干个数,而且以 xxx 为开头的非递减数列的数量。

我们只考虑 xxx 有多少种可能的值(实际只有 O(105)O(\sqrt{10^5})O(105),后面会提到)。对于 x=1x=1x=1 ~ 10510^5105,需要进行如下的状态转移:dp(i,⌊ai⌈aix⌉⌋)+=dp(i+1,x)dp\Bigg(i,\Bigg\lfloor\frac{a_i}{\big\lceil\frac{a_i}{x}\big\rceil}\Bigg\rfloor\Bigg)+=dp(i+1,x)dp(i,xaiai)+=dp(i+1,x)

实际上 xxx 最多只有 21052\sqrt{10^5}2105 种不同的值(⌊m1⌋,⌊m2⌋,⋯ ,⌊mm⌋\lfloor\frac{m}{1}\rfloor,\lfloor\frac{m}{2}\rfloor,\cdots,\lfloor\frac{m}{m}\rfloor1m,2m,,mm,整除分块的思想)。因此我们可以在 O(n105)O(n\sqrt{10^5})O(n105) 的时间复杂度内解决此题,而且转移方程之和 i,i+1i,i+1i,i+1 有关,可以使用滚动数组优化掉一维。

最后根据 dpdpdp 数组计算答案,对于 dp(i+1,x)dp(i+1,x)dp(i+1,x),它对答案的贡献为 i×dp(i+1,x)×(⌈aiai+!⌉−1)i\times dp(i+1,x)\times(\lceil\frac{a_i}{a_{i+!}}\rceil-1)i×dp(i+1,x)×(ai+!ai1),这是因为有 i×dp(i+1,x)i\times dp(i+1,x)i×dp(i+1,x) 个序列,每个序列都对 aia_iai 进行这样的拆分。

时间复杂度 O(n105)O(n\sqrt{10^5})O(n105),空间复杂度 O(n)O(n)O(n)

代码

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>

using namespace std;
using namespace __gnu_pbds;
const int mod = 998244353;
int a[300010];

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        gp_hash_table<int, int> dp;
        long long ans = 0;
        for (int i = n; i >= 1; i--) {
            gp_hash_table<int, int> &&dp2 = {};
            dp2[a[i]] = 1;
            for (auto it:dp) {
                int t = (a[i] + it.first - 1) / it.first;
                dp2[a[i] / t] += it.second;
                ans = ans + ((long long) i * it.second) * (t - 1);
            }
            ans = ans % mod;
            dp = move(dp2);
        }
        cout << ans << endl;
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值