Codeforces Round 1112 Div2 B-D 题解

B - String Construction

题意简述

构造长度为 \(n\)01\(s\), 使得 01 的个数至多相差 \(1\) 且相邻且相同的字符的对数为 \(k.\)

分析

不妨令 0 的个数大于等于 1 的个数 \(m = n/2\),考虑在 0 中插入 1, 插入的位置从 0 串的左侧到每个间隔再到右侧,每次插入 \(a_i(a_i\ge 0)\) 个,有 \(\sum_{i=1}^{n - m + 1} a_i = m,\) 而插入的每个位置对相邻且相同字符对数的贡献 \(c_i\) 满足:

\[c_i = \begin{cases} 0 & a_i = 0 \\ a_i - 1 & a_i > 0 \land (i = 1,n - m + 1) \\ a_i - 2 & a_i > 0 \land 1 < i < n - m + 1 \end{cases} \]

那么要使得 \(\sum c_i + n - m - 1 = k,\) 即是使 \(\sum a_i - x - 2y + n - m - 1 = n - 1 - x - 2y = k,\) 其中 \(x\)0 串的两侧选择的位置的个数, \(y\)0 串的间隔中选择的位置的个数,且满足 \(0\le x\le 2,0\le y\le n - m - 1,0<x+y\le m.\)
如此一来,只需枚举 \(x\) 求出 \(y\) 再进行输出即可。但是代码实现极其恶心。

#include <bits/stdc++.h>
using namespace std;
int n, k;
string s;
int main() {
    int tt;
    cin >> tt;
    while(tt--) {
        cin >> n >> k;
        s = "";
        for (int i = 0; i < n; ++i) s += 'x';
        bool flag = 0;
        for (int x = 0; x <= 2; ++x) {
            if ((n - 1 - x - k) % 2 == 0) {
                int y = (n - 1 - x - k) / 2;
                if (y > (n - n / 2 - 1) || x + y == 0 || y < 0 || x + y > n / 2) continue;
                if (x == 0) {
                    int cnt = 1;
                    s[0] = '0';
                    for (int i = 1; i <= n / 2 - y + 1; ++i) {
                        s[i] = '1'; cnt++;
                    } for (int i = 1; i < y; ++i) {
                        s[cnt++] = '0'; s[cnt++] = '1';
                    } for (int i = cnt; i < n; ++i) s[i] = '0';
                    cout << s << endl;
                    flag = 1; break;
                } else if (x == 1) {
                    if (y != 0) {
                        s[0] = '1', s[1] = '0';
                        int cnt = 2;
                        for (int i = 2; i <= n / 2 - y + 1; ++i) {
                            s[i] = '1'; cnt++;
                        } for (int i = 1; i < y; ++i) {
                            s[cnt++] = '0'; s[cnt++] = '1';
                        } for (int i = cnt; i < n; ++i) s[i] = '0';
                        cout << s << endl;
                        flag = 1; break;
                    } else {
                        for (int i = 0; i < n / 2; ++i) s[i] = '1';
                        for (int i = n / 2; i < n; ++i) s[i] = '0';
                        cout << s << endl;
                        flag = 1; break;
                    }
                } else if (x == 2) {
                    if (y != 0) {
                        s[0] = s[n - 1] = '1', s[1] = '0';
                        int cnt = 2;
                        for (int i = 2; i <= n / 2 - y; ++i) {
                            s[i] = '1'; cnt++;
                        } for (int i = 1; i < y; ++i) {
                            s[cnt++] = '0'; s[cnt++] = '1';
                        } for (int i = cnt; i < n - 1; ++i) s[i] = '0';
                        cout << s << endl;
                        flag = 1; 
                    } else {
                        for (int i = 0; i < n / 2 - 1; ++i) s[i] = '1';
                        for (int i = n / 2 - 1; i < n - 1; ++i) s[i] = '0';
                        s[n - 1] = '1';
                        cout << s << endl;
                        flag = 1;
                    }
                } 
            }
        } if (flag == 0) cout << -1 << endl;
    }
    return 0;
}

以上是赛时做法,接下来我们介绍一种更普遍的做法。考虑 01 串一共被分成了 \(m(m=2,3,\dots,n)\) 01 块,则相邻且相同的字符对数就是 \(n - m.\) 实现起来也相当容易。

相比之下,这个思路就显得更加自然且容易得多了,让人不禁对笔者的赛时做法感到不可思议。

人怎么能有如此扭曲的想法!

#include <bits/stdc++.h>
using namespace std;
int n, k;
int main() {
    int tt;
    cin >> tt;
    while (tt--) {
        cin >> n >> k;
        if (k == n - 1) cout << -1 << endl;
        else {
            for (int i = 1; i <= k / 2 + 1; ++i) cout << 1;
            for (int i = 1; i <= k / 2 + 1 + k % 2; ++i) cout << 0;
            for (int i = 3; i <= n - k; ++i) cout << i % 2;
            cout << endl;
        }
    }
    return 0;
}

C - Rank Subsequence

题意简述

对于一个长度为 \(n\) 的序列,求最长的子序列,满足第 \(i\) 个元素左起下标不在 \([l_i,r_i]\) 内,右起下标不在 \([u_i,v_i]\) 内。

分析

\(m\) 固定的时候,子序列中的每个元素的左起下标和右起下标都被固定了。于是我们可以清楚地知道原序列中的哪些元素可以当子序列中的第 \(i\) 个元素。我们可以从左向右地考虑子序列中的每个元素,每次只需要在原序列中贪心地选择在上一次选择的元素右侧的最靠左的可行的元素,这样一来我们就能知道是不是真的能选出 \(m\) 个,即 \(m\) 是不是一个可行的解。从大到小枚举 \(m\) 即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int n, p[N][4];
int main() {
    int tt;
    cin >> tt;
    while (tt--) {
        cin >> n;
        for (int i = 1; i <= n; ++i) 
            for (int j = 0; j < 4; ++j)
                cin >> p[i][j];
        bool flag = 0;
        for (int m = n; m > 0; --m) {
            int cnt = 1;
            for (int i = 1; i <= n; ++i) {
                if ((cnt < p[i][0] || cnt > p[i][1]) &&
                    (m - cnt + 1 < p[i][2] || m + 1 - cnt > p[i][3])) {
                    if (cnt == m) { cout << m << endl; flag = 1; break; }
                    cnt++;
                }
            } if (flag) break;
        } if (!flag) cout << 0 << endl;
    }
    return 0;
}

D - Permutation Cuts

题意简述

\(n\) 和序列 \(a,\) 问有多少可行的长度为 \(n\) 的全排列满足 \(v_i = a_i,\) 其中 \(v_i\)\(p_1\)\(p_i\)\(p_{i+1}\)\(p_n\) 最大值中较小的那个。

分析

容易想到排列中 \(n\) 左侧的位置 \(i\) 满足 \(v_i\) 是从 \(p_1\)\(p_i\) 的最大值,\(n\) 及其右侧的位置 \(i\) 满足 \(v_i\)\(p_i\)\(p_n\) 的最大值。对于给定的 \(a,\) 如果 \(a\) 是合法的,那么 \(a\) 至多是单峰的。在保证 \(a\) 至多的是单峰之后,剩下的事情就交给排列组合。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 5, M = 998244353;
int a[N], vh[N], n;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int tt;
    cin >> tt;
    while (tt--) {
        for (int i = 1; i <= n; ++i) vh[i] = 0;
        cin >> n;
        for (int i = 1; i < n; ++i) cin >> a[i];
        a[n] = 0;
        bool status = 0, flag = 1;
        int tor = 1;
        for (int i = 1; i < n; ++i) {
            if (a[i] == n) { flag = 0; break; }
            if (!status) {
                vh[a[i]]++;
                if (a[i] > a[i + 1]) { tor = i; status = 1; }
            } else {
                if (vh[a[i]] || a[i] < a[i + 1]) { flag = 0; break; }
            } 
        } 
        if (!flag) { cout << 0 << endl; continue; }
        int cnt = 1;
        int p = 1, q = n - 1;
        while (p <= tor || q > tor) {
            int f = p + n - 2 - q;
            if (p > tor || q > tor && a[q] < a[p]) {
                if (f >= a[q]) { flag = 0; break; }
                if (a[q] == a[q + 1]) (cnt *= a[q] - f) %= M; q--;
            } else {
                if (f >= a[p]) { flag = 0; break; }
                if (a[p] == a[p - 1]) (cnt *= a[p] - f) %= M; p++;
            }
        } if (flag) cout << (cnt * 2) % M << endl;
        else cout << 0 << endl;
    } 
    return 0;
}
posted @ 2026-07-29 01:02  尚晨月  阅读(7)  评论(0)    收藏  举报