【寒假复健Day5】Codeforces Round 1003 (Div. 4) - 题解

Codeforces Round 1003 (Div. 4) - 题解

Codeforces Round 1003 (Div. 4)

A - Skibidus and Amog’u

模拟

​ 如题意,输出前 n − 2 n-2 n2 个字符 + i 即可。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>

using namespace std;
int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        string str;
        cin >> str;
        cout << str.substr(0, str.size() - 2) << "i\n";
    }
}

B - Skibidus and Ohio

思维

​ 我称之为 “点燃” 类,出题思路常用于简单题。一旦 出现相邻两个字符串相同,其中一个被删除,字符串长度 − 1 -1 1,另一个变为左侧或右侧字符,则必然与该侧字符一起再次达成 “相邻两个字符串相同” 的条件,循环往复,直至只剩一个字符。

​ 判断字符串中是否存在相邻两字符相同,若存在则输出 1 1 1,反之为字符串长度。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>

using namespace std;
int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        string str;
        cin >> str;
        int res = str.length();
        for (int i = 1; i < res; i++)
            if (str[i] == str[i - 1]) res = 1;
        cout << res << '\n';
    }
}

C - Skibidus and Fanum Tax

贪心,排序,二分

​ 从左往右考虑,当前数变得越小,则后续 “选择余地” 只会越大,文绉绉地说,这叫贪心的 “决策包容性”。故我们依次考虑每个 a i a_i ai 选择让其尽可能小。

​ 对于 a i a_i ai,若进行操作,则变为 b j − a i b_j-a_i bjai。由于限制 + 贪心策略,需找出 b j − a i ≥ a i − 1 b_j-a_i\geq a_{i-1} bjaiai1 下最小的 b j − a i b_j-a_i bjai,即找出 b j ≥ a i − 1 + a i b_j\geq a_{i-1}+a_i bjai1+ai 下最小的 b j b_j bj. 将数组 b 排序,再二分搜索即可,可使用 lower_bound.

​ 对于可行(即 ≥ a i − 1 \geq a_{i-1} ai1的)的 a i a_i ai b j − a i b_j-a_i bjai,选择较小者作为更新后的 a i a_i ai,若二者均不可行(即都 < a i − 1 <a_{i-1} <ai1),则无法构成,输出 NO。反之构造完毕,则输出 YES

  • 时间复杂度: O ( m + n log ⁡ m ) O(m+n\log{m}) O(m+nlogm)
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m)
#include<bits/stdc++.h>

using namespace std;
const int N = 2e5;
int a[N], b[N];

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        int n, m, last = INT_MIN + 1, res = 1;
        cin >> n >> m;
        for (int i = 0; i < n; i++) cin >> a[i];
        for (int i = 0; i < m; i++) cin >> b[i];
        sort(b, b + m);
        for (int i = 0; i < n; i++) {
            int tar = lower_bound(b, b + m, a[i] + last) - b;
            if (tar == m) {
                if (a[i] < last) res = 0;
                last = a[i];
            } else {
                if (a[i] < last) last = b[tar] - a[i];
                else last = min(a[i], b[tar] - a[i]);
            }
        }
        cout << (res ? "YES\n" : "NO\n");
    }
}

D - Skibidus and Sigma

搜索

​ 这是一个经典贪心策略的弱化版(一开始我误理解为下面的改造版)。

​ 其答案为 ∑ i = 1 n ( n − i + 1 ) × a i = n a 1 + ( n − 1 ) a 2 + ⋯ + a n \sum_{i=1}^n (n-i+1)\times a_i=na_1+(n-1)a_2+\cdots+a_n i=1n(ni+1)×ai=na1+(n1)a2++an,用 v i v_i vi 表示第 i i i 个数组, s i s_i si 表示 v i v_i vi 的和。若数组向左移动 1 1 1 单位,则该数组每个元素系数 + 1 +1 +1,整体和 + s i +s_i +si,反之向右移动 1 1 1 单位,整体和 − s i -s_i si.

​ 考虑数组序列 ⋯ v i v i + 1 ⋯ \cdots v_iv_{i+1}\cdots vivi+1,若交换 v i v i + 1 v_iv_{i+1} vivi+1,因每个数组长度为 m m m ,则对答案的贡献为 m s i + 1 − m s i = m ( s i + 1 − s i ) ms_{i+1}-ms_i=m(s_{i+1}-s_i) msi+1msi=m(si+1si),故数组和越大,则优先级越高,按数组和降序即可。

  • 时间复杂度: O ( n m log ⁡ m ) O(nm\log{m}) O(nmlogm)
  • 空间复杂度: O ( n m ) O(nm) O(nm)
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        LL n, m;
        cin >> n >> m;
        vector<pair<LL, vector<LL>>> arr(n, { 0, vector<LL>(m)});
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++)
                cin >> arr[i].second[j];
            arr[i].first = accumulate(arr[i].second.begin(), arr[i].second.end(), 0LL);
        }
        sort(arr.rbegin(), arr.rend());
        LL idx = n * m, res = 0;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                res += arr[i].second[j] * idx--;
        cout << res << '\n';
    }
}

​ 我们对问题加以改造,若每个数组长度并不定长呢?同样考虑数组序列 ⋯ v i v i + 1 ⋯ \cdots v_iv_{i+1}\cdots vivi+1,其 s i , s i + 1 s_i,s_{i+1} si,si+1 表示二者的数组和, l i , l i + 1 l_i,l_{i+1} li,li+1 表示二者数组长度,若交换 v i vi vi v i + 1 v_{i+1} vi+1 的位置,则对答案的影响为 l i s i + 1 − l i + 1 s i l_is_{i+1}-l_{i+1}s_i lisi+1li+1si,排序时根据该值进行比较即可。也可进一步变换,若 l i s i + 1 − l i + 1 s i > 0 l_is_{i+1}-l_{i+1}s_i>0 lisi+1li+1si>0 则交换,也即 s i l i > s i + 1 l i + 1 \frac{s_i}{l_i}>\frac{s_{i+1}}{l_{i+1}} lisi>li+1si+1,即根据数组的平均值排序即可。

将第 19 19 19 行代码改为:

sort(arr.rbegin(), arr.rend(), [](const pair<LL, vector<LL>> &a, const pair<LL, vector<LL>> &b) {
            return a.first * b.second.size() < a.second.size() * b.first;
        });

对输入输出的双重 for 循环略作修改即可。

​ 基本版:P1223 排队接水

​ 相同思想的题目仍有很多,该类题目答案情形为序列(数组)形式,交换其中相邻两项,并不会其他部分的 “贡献”(对答案的计算)产生影响,则在该局部(这两项中)采取对答案贡献最大的排位。

​ 看似,若选择序列中任意两项(中间跨越若干项),则可能对其余部分产生影响(如 P1223 中),使得难以比较;同样,比较的性质也不一定像本题的改造版这样直观,可以归约到只与自身有关(数组本身的平均值),不能直接推广至全局。但为啥只要实现相邻两项比较加上 sort 就能得到全局最优?

​ 因为其还有一性质 —— 偏序性

建议百度以获得严格的定义与理解。

给一集合 S S S,任选两个元素 a , b ( a ≠ b ) a,b(a\neq b) a,b(a=b) ,你总有一个函数 f ( a , b ) f(a,b) f(a,b),或者说法则、方法等便于理解的称呼,使得输出 a a a b b b 哪一个 “更重要” 或类似的一种指向 关系

  • a a a b b b “更重要”,则 b b b 不可能 a a a “更重要”,即这个谁比谁更重要的关系是单向的,即 非对称性 的。(看似是废话,因为便于理解,选用 “更重要” 该词作为关系的名称,此词的语义即包含了偏序的意味)
  • a a a b b b “更重要” b b b c c c “更重要”,则 a a a c c c “更重要”,即 传递性
  • 自己不比自己 “更重要”,即 “反自反性”

满足这三个条件关系的即为 严格偏序关系

如整数集合上的 大于 关系、小于 关系则是严格偏序关系。

对上述条件加以改造,则可得到 非严格偏序关系,如整数集合上的 大于等于 关系、小于等于 关系。

​ 对于集合 S S S 而言,想象中,我们可以根据 偏序关系 将其排位一列,“更重要的” 在前,更不重要的在后(我也不知道你想象中的前后是哪)。而 sort 或者说排序算法,则可对集合中的若干元素,按照 偏序关系 排好。如普通的 sort 可以将实数数组,按 小于 关系升序排列。

​ 如果理解有困难,则可以把抽象集合 S S S 中的每个元素,标以(映射到)一个实数,上面那个 “更重要” 的关系想象成 小于 关系,那 sort 显然可以把这玩意排好序。

​ 回到题目,上述比较方法,实际是一个 非严格偏序关系,证明下对任意数组构成的集合 S S S,该比较方法,满足 自反性反对称性传递性(此为 非严格偏序关系 的三性)即可使用 sort 排序。

​ 赛时怎么办?我们一般不证,直接用。当然此处我也没有证明, 非严格偏序关系 只是猜测。

想象下,给 sort 传入一破坏了传递性的比较函数,可能发生什么?

E - Skibidus and Rizz

构造

​ 即任意一子串,其 0 0 0 个数与 1 1 1 个数之差即为平衡值。构造一 0 / 1 0/1 0/1 串,使所有子串的最大平衡值 恰为 k k k(每个子串的平衡值 ≤ k \leq k k,同时有至少一个子串平衡值为 k k k)。

​ 若 m a x { n , m } < k max\{n,m\}<k max{n,m}<k,因平衡值最大只能为 m a x { n , m } max\{n,m\} max{n,m},输出 − 1 -1 1.

​ 若 ∣ n − m ∣ > k |n-m|>k nm>k,因整个 0 / 1 0/1 0/1 串的平衡值为 ∣ n − m ∣ |n-m| nm,输出 − 1 -1 1.

​ 排除上述两条件,其余情况均可构造,考虑 k k k 0 0 0 k k k 1 1 1,不足则全部输出。证明略。

  • 时间复杂度: O ( n + m ) O(n+m) O(n+m)
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m)
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        int n, m, k;
        cin >> n >> m >> k;
        if (abs(n - m) > k || max(n, m) < k) cout << "-1\n";
        else {
            if (m > n) {
                cout << string(min(m, k),'1');
                m -= k;
            }
            while (n > 0) {
                cout << string(min(n, k),'0');
                if (m > 0) cout << string(min(m, k),'1');
                n -= k;
                m -= k;
            }
            cout << '\n';
        }
    }
}

F - Skibidus and Slay

图论

非平凡简单路径(non-trivial simple path),非平凡(non-trivial)指路径中点数不为 1 1 1

​ 给一条长为 n ≥ 2 n\geq2 n2的(非平凡)简单路径,假定其 多数 k k k.

​ 若其中出现两相邻点,值均为 k k k,则在考虑由这两点加之其连边构成的长为 2 2 2 的简单路径时,即可得到一 多数 k k k.

​ 若其中并未出现两相邻元素均为 k k k,则 k k k 必定在数组中交替,形如以下:

n n n 为奇: k , ? , k , ⋯   , ? , k k,?,k,\cdots,?,k k,?,k,,?,k

n n n 为偶: k , ? , k , ⋯   , k , ? k,?,k,\cdots,k,? k,?,k,,k,?(其翻转形式同)

​ 我们只需考虑由该路径前 3 3 3 个点及其 2 2 2 条连边的子路径(或者说一定存在这样一条子路径),则可得到一 多数 k k k.

​ 由上,只需考虑长度为 2 2 2 3 3 3 的路径,即可将所有简单路径产生的 多数 考虑 完备

​ 更进一步,我们只需考虑对每个点,及其周围一圈的所有点(构成菊花)的数计数,统计出现次数 ≥ 2 \geq2 2 的数,即等价考虑长度为 2 2 2 3 3 3 的路径的 多数

记某点为中心点,其周围一圈(仅经过一条路径到达)的点记为边缘点。

在某一中心点及其所有边缘点构成的图中,若 k k k 出现次数 ≥ 2 \geq2 2,则至少构成如下情形:

  • 中心点与一边缘点均为 k k k,出现上述情形 1 1 1
  • 两边缘点为 k k k,则这两点通过中心点连接,出现上述情形 2 2 2
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>

using namespace std;
const int N = 5e5 + 1, M = 2 * N;
int arr[N];
int head[N], val[M] , linked[M], last = 1;
bool res[N];

void add(int a, int b) {
    linked[last] = head[a];
    val[last] = b;
    head[a] = last++;
}

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        last = 1;
        for (int i = 1; i <= n; i++) {
            res[i] = false;
            head[i] = 0;
            cin >> arr[i];
        }
        for (int i = 1; i < n; i++) {
            int a, b;
            cin >> a >> b;
            add(a, b);
            add(b, a);
        }
        for (int v = 1; v <= n; v++) {
            map<int, int> cnt;
            cnt[arr[v]]++;
            for (int i = head[v]; i; i = linked[i])
                if (++cnt[arr[val[i]]] >= 2) res[arr[val[i]]] = true;
        }
        for (int i = 1; i <= n; i++)
            cout << (res[i] ? "1" : "0");
        cout << '\n';
    }
}

G - Skibidus and Capping

数论

​ 从左到右依次考虑:

  • a i a_i ai 为素数 p p p,则可与左侧带有 p p p 的半素数 p q pq pq 构成 l c m ( p q , p ) = p q lcm(pq,p)=pq lcm(pq,p)=pq,也可与不为 p p p 的素数 q q q 构成 l c m ( q , p ) = p q lcm(q,p)=pq lcm(q,p)=pq

  • a i a_i ai 为素数 p p p 的平方 p 2 p^2 p2,则可与左侧的 p p p p 2 p^2 p2 构成 l c m ( p , p 2 ) = l c m ( p 2 , p 2 ) = p 2 lcm(p,p^2)=lcm(p^2,p^2)=p^2 lcm(p,p2)=lcm(p2,p2)=p2

  • a i a_i ai 为两素数 p , q p,q p,q 乘积 p q pq pq,则可与左侧的 p p p q q q p q pq pq 构成 l c m ( p , p q ) = l c m ( q , p q ) = l c m ( p q , p q ) = p q lcm(p,pq)=lcm(q,pq)=lcm(pq,pq)=pq lcm(p,pq)=lcm(q,pq)=lcm(pq,pq)=pq

    综上,先筛出 1 ∼ 2 × 1 0 5 1\sim2\times10^5 12×105 内的素数,然后维护每个数出现次数、素数出现次数、含每个因子的半素数出现次数即可。

  • 时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n)

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2e5 + 1;
int factor[N], prime[N], cnt = 0;

void init(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!factor[i]) factor[i] = prime[cnt++] = i;
        for (int j = 0; prime[j] <= factor[i]; j++) {
            if ((LL)i * prime[j] > n) break;
            factor[i * prime[j]] = prime[j];
            if (i % prime[j] == 0) break;
        }
    }
}

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    init(N - 1);
    int t;
    cin >> t;
    while (t--) {
        LL n, res = 0, cp = 0;
        cin >> n;
        map<LL,LL> cnt, fac;
        for (int i = 0; i < n; i++) {
            LL x;
            cin >> x;
            cnt[x]++;
            LL p = factor[x], q = x / factor[x];
            if (q == 1) {
                cp++;
                res += fac[p] + cp - cnt[p];
            } else if (p == q) {
                res += cnt[p] + cnt[p * p];
                fac[p]++;
            }  else if (factor[q] == q) {
                res += cnt[p] + cnt[q] + cnt[x];
                fac[q]++;
                fac[p]++;
            }
        }
        cout << res << '\n';
    }
}

H - Bro Thinks He’s Him

树状数组

​ 这是一道典型的算 贡献 的问题,我们把 f ( x ) f(x) f(x) 称之为 01 01 01 x x x贡献。其定义值串中连续 0 0 0 或连续 1 1 1 的段的数量,可以进一步理解为为串中 0 / 1 0/1 0/1 交替次数 + 1 +1 +1,交替次数即 0110 出现次数。

​ 我们先看由 0 0 0 1 1 1 的情况,由其对称性, 1 1 1 0 0 0 的情况易得。

​ 微观上,一 01 01 01 串某位发生反转,对其贡献的影响只需看该位与相邻位的情况即可(由第一段,其贡献可以视为统计0110 出现次数 + 1 +1 +1,某一位的改变,显然不会影响更远位置的情况),我们枚举所有情况,观察对每种序列贡献的影响。

​ 比如 ⋯ 0 0 ‾ \cdots 0\underline{0} 00 的情况(反转位为序列尾,左侧为 0 0 0),此时该种序列的贡献 + 1 +1 +1;再如 ⋯ 1 0 ‾ 1 ⋯ \cdots1\underline{0}1\cdots 101(反转位左右两侧均为 1 1 1),此时该种序列的贡献 + 2 +2 +2。如此,反转位左右两侧可能为 0 0 0 1 1 1、无,枚举总共有 3 × 3 = 9 3\times3=9 3×3=9 种情况,见下表。

​ 宏观上,我们将 每种序列的贡献影响该种序列数量 相乘即可得到对全局贡献的影响(好似一句废话)。 ⋯ 0 0 ‾ \cdots 0\underline{0} 00 的情况中,该类型序列数量为 反转位左侧以 0 0 0 结尾的序列数 ⋯ 1 0 ‾ 1 ⋯ \cdots1\underline{0}1\cdots 101 则是 反转位左侧以 1 1 1 结尾的序列数 × 反转位右侧以 1 1 1 开始的序列数

​ 因为具体到某种序列中,某侧位无的情况相当于 1 1 1。故我们只需统计左侧以 0 / 1 0/1 0/1 结尾的序列数,以及右侧以 0 / 1 0/1 0/1 开头的序列数,共 4 4 4 种,再组合下即可。

​ 给一 01 01 01 串,其第 i i i 位左侧以 0 0 0 结尾的序列个数为:考虑 i i i 左侧所有出现 0 0 0 的位置,假设第 j j j 处为 0 0 0,则以该位 0 0 0 结尾的序列个数为 2 j − 1 2^{j-1} 2j1 (左侧 j − 1 j-1 j1 0 / 1 0/1 0/1 任意选择),求和即可。另外三种情况类似。前缀和预处理即可,对于每次询问查询,但因每次查询会实际修改,故使用树状数组(或线段树等) 维护单点修改前缀和

​ 我们计左侧为 l l l,右侧为 r r r,出现 0 / 1 0/1 0/1 以角标标注,如 l 0 l_0 l0 表示 左侧以 0 0 0 结尾的序列数

序列类型贡献影响出现次数
0 ‾ \underline{0} 0 0 0 0 1 1 1
⋯ 0 0 ‾ \cdots0\underline{0} 00 + 1 +1 +1 l 0 l_0 l0
⋯ 1 0 ‾ \cdots1\underline{0} 10 − 1 -1 1 l 1 l_1 l1
0 ‾ 0 ⋯ \underline{0}0\cdots 00 + 1 +1 +1 r 0 r_0 r0
0 ‾ 1 ⋯ \underline{0}1\cdots 01 − 1 -1 1 r 1 r_1 r1
⋯ 0 0 ‾ 0 ⋯ \cdots0\underline{0}0\cdots 000 + 2 +2 +2 l 0 r 0 l_0r_0 l0r0
⋯ 0 0 ‾ 1 ⋯ \cdots0\underline{0}1\cdots 001 0 0 0 l 0 r 1 l_0r_1 l0r1
⋯ 1 0 ‾ 0 ⋯ \cdots1\underline{0}0\cdots 100 0 0 0 l 1 r 0 l_1r_0 l1r0
⋯ 1 0 ‾ 1 ⋯ \cdots1\underline{0}1\cdots 101 − 2 -2 2 l 1 r 1 l_1r_1 l1r1

​ 故将某位由 0 0 0 1 1 1 对答案产生的影响为 l 0 + r 0 + 2 l 0 r 0 − ( l 1 + r 1 + 2 l 1 r 1 ) l_0+r_0+2l_0r_0-(l_1+r_1+2l_1r_1) l0+r0+2l0r0(l1+r1+2l1r1).

1 1 1 0 0 0 的情况对称即可。

  • 时间复杂度: O ( n + q log ⁡ n ) O(n+q\log{n}) O(n+qlogn)
  • 空间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
#define fix(x) (((x) % MOD + MOD) % MOD)

using namespace std;
typedef long long LL;
const int MOD = 998244353;
const int N = 2e5 + 1;
LL n, res;
string s, str;

struct Tree {
    LL bt[N];

    void add(int x, LL num) {
        for (; x <= n; x += (x & -x)) bt[x] = fix(bt[x] +num);
    }

    LL get(int x) {//[1,x]
        LL res = 0;
        for (; x; x -= (x & -x)) res = fix(res + bt[x]);
        return res;
    }

    LL get(int l, int r) {//[l,r]
        return fix(get(r) - get(l - 1));
    }
} sum[4]; //l1,r1

LL pow_(LL b, LL p) {
    LL res = 1 % MOD;
    while (p) {
        if (p & 1) (res *= b) %= MOD;
        (b *= b) %= MOD;
        p >>= 1;
    }
    return res;
}

void modify(int idx) {
    LL l1 = sum[0].get(idx - 1);
    LL r1 = sum[1].get(n - idx);
    LL l0 = fix(pow_(2, idx - 1) - 1 - l1);
    LL r0 = fix(pow_(2, n - idx) - 1 - r1);
    LL ans = l0 + r0 + 2 * l0 * r0 - l1 - r1 - 2 * l1 * r1;
    LL f = s[idx] == '0' ? 1 : -1;
    res = fix(res + f * ans);
    sum[0].add(idx, f * pow_(2, idx - 1));
    sum[1].add(n - idx + 1, f * pow_(2, n - idx));
    s[idx] ^= 1;
}

int main() {
    cin.tie(NULL)->sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        int tt;
        cin >> str >> tt;
        n = str.length();
        s = string(n + 1, '0');
        res = pow_(2, n) - 1;
        for (int i = 0; i <= n; i++)
            sum[0].bt[i] = sum[1].bt[i] = 0;
        for (int i = 0; i < n; i++)
            if (str[i] == '1') modify(i + 1);
        while (tt--) {
            int idx;
            cin >> idx;
            modify(idx);
            cout << res << ' ';
        }
        cout << '\n';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值