2022牛客冬令营 第三场 题解

A题 智乃的Hello XXXX(签到)

输出 \(\text{hello xxx}\),其中 xxx 可以是任意字符串

随便输出个啥来签下到就行,不过还是建议跟一个 ASCII码构成的字符串,防止乱码。

print("hello world")

B题 智乃买瓜(01背包)

现在有 \(n\) 个瓜,第 \(i\) 个瓜的质量为 \(w_i\)

现在,对于每个瓜,我们可以选择:

  1. 买下这个瓜
  2. 把瓜劈一半,然后只买一半
  3. 不买

现在我们想要知道,当我们买的瓜的质量和为 \(1,2,\cdots,M\) 时,分别有几种方案?

\(0\leq n\leq 10^3,1\leq M \leq10^3,2\leq w_i\leq 2*10^3\),保证 \(w_i\) 为偶数

不劈瓜的话就是典型 01 背包统计方案数问题,劈瓜的话其实也差不多。

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int n, m, w[N];
int dp[N][N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> w[i];
    dp[0][0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= m; ++j) {
            dp[i][j] = dp[i - 1][j];
            if (j >= w[i])
                dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i]]) % mod;
            if (j >= w[i] / 2)
                dp[i][j] = (dp[i][j] + dp[i - 1][j - w[i] / 2]) % mod;
        }
    for (int i = 1; i <= m; ++i)
        printf("%d ", dp[n][i]);
    return 0;
}

C题 智乃买瓜(another version) (逆DP)

本题和上面同一背景,不过这题是给定购买质量和为 \(1,2,\cdots,M\) 时候的方案数(对 \(10^9+7\) 取模),让我们反过来构造出这些瓜的质量。

\(1\leq M \leq 10^3\)

对于给定的方案数,我们显然能找到某个物品的 \(w\):倘若质量和为 \(x\) 的方案数大于 0,而质量和小于 \(x\) 的方案数均为 0,那么说明必然有一个质量为 \(2x\) 的瓜,我们将其存入答案数组中。

我们知道 B 题的 DP 方程为:

\[dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-w_i}+dp_{i-1,j-\frac{w_i}{2}} \]

那么移项并变换,得

\[dp_{i-1,j}=dp_{i,j}-dp_{i-1,j-w_i}-dp_{i-1,j-\frac{w_i}{2}}\\ dp_{i,j}=dp_{i + 1,j}-dp_{i,j-w_{i+1}}-dp_{i,j-\frac{w_{i+1}}{2}} \]

这样,我们得到了原 DP 方程的逆 DP 方程,照着推即可。

当整个 DP 数组变为全 0 时,结束流程,输出答案数组中记录的数即可。

#include<bits/stdc++.h>
using namespace std;
const int M = 1010;
const int mod = 1e9 + 7;
int m, dp[2][M];
vector<int> ans;
int main()
{
    dp[0][0] = dp[1][0] = 1;
    cin >> m;
    for (int i = 1; i <= m; ++i) cin >> dp[0][i];
    for (int i = 1; ;++i) {
        int w = -1;
        for (int j = 1; j <= m && w == -1; ++j)
            if (dp[(i - 1) % 2][j]) w = 2 * j;
        if (w == -1) break;
        ans.push_back(w);
        for (int j = 1; j <= m; ++j) {
            dp[i % 2][j] = dp[(i - 1) % 2][j];
            if (j >= w / 2) {
                dp[i % 2][j] -= dp[i % 2][j - w / 2];
                //减的时候如果变成负数,要加上mod,下面同理
                if (dp[i % 2][j] < 0) dp[i % 2][j] += mod;
            }
            if (j >= w) {
                dp[i % 2][j] -= dp[i % 2][j - w];
                if (dp[i % 2][j] < 0) dp[i % 2][j] += mod;
            }
        }
    }
    cout << ans.size() << endl;
    for (int w : ans) cout << w << ' ';
    return 0;
}

D题 智乃的01串打乱(签到)

把一个纯 01 串打乱,保证字符串并非全 0 或全 1。

找到一个 1 和 一个 0 的位置,交换一下即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
char s[N];
int main()
{
    scanf("%d%s", &n, s + 1);
    for (int i = 1; i < n; ++i)
        if (s[i] != s[i + 1]) {
            swap(s[i], s[i + 1]);
            break;
        }
    puts(s + 1);
    return 0;
}

E题 智乃的数字积木(easy version)(排序,模拟)

现在有 \(n\) 块积木,每块积木有一个一位数字和一个颜色。相邻两个颜色相同的积木可以更换位置。

现在我们有油漆,第 \(i\) 次可以将 \(p_i\) 颜色的积木全部涂成 \(p_i\)

问:在 \(k\) 次涂油漆后,每次分别都可以将积木排列出的最大值(从左往右读出来的十位数,允许前导 0)是多少?(包括最初情况和每次涂油漆之后的情况)

\(1\leq n,m\leq 10^5,0\leq k\leq 10\)

突然发现,每个积木只会在自己所在的那个颜色区块里面,那么直接找出来,然后硬排序即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
#define LL long long
const LL mod = 1e9 + 7;
int n, m, k;
struct Node {
    int color, num;
    bool operator < (const Node &rhs) const {
        return num > rhs.num;
    }
} a[N];
//
void change(int p, int q) {
    for (int i = 1; i <= n; ++i)
        if (a[i].color == p) a[i].color = q;
}
Node t[N];
LL solve() {
    for (int i = 1; i <= n; ++i) t[i] = a[i];
    //
    int L = 1;
    while (L <= n) {
        int R = L;
        while (R <= n && t[R].color == t[L].color) R++;
        sort(t + L, t + R);
        L = R;
    }
    LL res = 0;
    for (int i = 1; i <= n; ++i)
        res = (res * 10 + t[i].num) % mod;
    return res;
}
char s[N];
int main()
{
    //read
    scanf("%d%d%d", &n, &m, &k);
    scanf("%s", s + 1);
    for (int i = 1; i <= n; ++i)
        a[i].num = s[i] - '0';
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i].color);
    //solve
    cout << solve() << endl;
    for (int i = 1; i <= k; ++i) {
        int p, q;
        scanf("%d%d", &p, &q);
        change(p, q);
        cout << solve() << endl;
    }
    return 0;
}

G题 智乃的树旋转(easy version)(模拟)

给定两个节点为 \(n\) 的二叉树,问能否通过多次旋转将前一棵二叉树变成后者?

\(n\leq 10^3\),保证答案不超过 1

保证答案不超过 1,\(n\) 的规模也不大,那就相当于考一手模拟旋转了(平衡树的知识忘得一干二净了属于是)

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
struct Node {
    int l, r;
} a[N], b[N], t[N];
int fa[N];
int check(int x) {
    return a[fa[x]].r == x;
}
void zag(int &B) {
    int A = fa[B], beta = t[B].l;
    t[A].r = beta, t[B].l = A;
    int X = fa[A];
    if (X) {
        if (t[X].l == A) t[X].l = B;
        else t[X].r = B;
    }
}
void zig(int &A) {
    int B = fa[A], beta = t[A].r;
    t[B].l = beta, t[A].r = B;
    int X = fa[B];
    if (X) {
        if (t[X].l == B) t[X].l = A;
        else t[X].r = A;
    }
}
void rorate(int x) {
    for (int i = 1; i <= n; ++i) t[i] = a[i];
    check(x) ? zag(x) : zig(x);
}
bool isAns() {
    int flag = true;
    for (int i = 1; i <= n && flag; ++i)
        if (t[i].l != b[i].l || t[i].r != b[i].r)
            flag = false;
    return flag;
}
int main()
{
    //read
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> b[i].l >> b[i].r;
    for (int i = 1; i <= n; ++i)
        cin >> a[i].l >> a[i].r;
    //solve
    //k=0
    for (int i = 1; i <= n; ++i)
        t[i] = a[i];
    if (isAns()) {
        puts("0");
        return 0;
    }
    //k=1
    for (int i = 1; i <= n; ++i) {
        int l = a[i].l, r = a[i].r;
        if (l) fa[l] = i;
        if (r) fa[r] = i;
    }
    for (int i = 1; i <= n; ++i)
        if (fa[i]) {
            rorate(i);
            if (isAns()) {
                printf("1\n%d", i);
                return 0;
            }
        }
    return 0;
}

当然,还有一种不需要旋转的方式:如果两个节点 \(i,j\) 在两棵树里面互为父子,那么肯定是旋转的他们,这时候输出原本是父亲的那个节点。

I题 智乃的密码(双指针)

给定一个长度为 \(n\) 的字符串,完全由大写字母,小写字母,数字和特殊字符构成。现在,我们想要选一个子串当密码,且要求:

  1. 密码长度在 \([L,R]\)
  2. 密码里面起码含三种及以上字符

\(1\leq n\leq 10^5,1\leq L\leq R\leq n\)

带限制的双指针,挺朴素的,就是调起来有点头疼。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, L, R;
char s[N];
//
int check(int c) {
    if (c >= 'A' && c <= 'Z') return 1;
    if (c >= 'a' && c <= 'z') return 2;
    if (c >= '0' && c <= '9') return 3;
    return 4;
}
int type[N];
int main()
{
    scanf("%d%d%d%s", &n, &L, &R, s + 1);
    for (int i = 1; i <= n; ++i)
        type[i] = check(s[i]);
    long long ans = 0;
    int vis[5] = {0, 0, 0, 0, 0};
    int l = 1, r = 0, cnt_type = 0;
    while (l <= n) {
        while (r < n && cnt_type < 3) {
            r++;
            vis[type[r]]++;
            if (vis[type[r]] == 1) cnt_type++;
        }
        //
        if (r == n && cnt_type < 3) break;
        while (l < r && cnt_type >= 3) {
            ans += max(min(n, l + R - 1) - max(r, l + L - 1) + 1, 0);

            vis[type[l]]--;
            if (vis[type[l]] == 0) cnt_type--;
            l++;
        }
    }
    cout << ans;
    return 0;
}

L题 智乃的数据库(模拟,map)

详情见题目

我们先用各种手段将指令里面那几个需要的字符串分离出来(substr 之后再 split,确实烦),然后直接模拟就是了(一切关于字符串的模拟,统统上 map,永远的神)

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
string title[N];
map<string, int> vis;
int Data[N][N];
vector<string> split_cmd()
{
    string cmd;
    getline(cin, cmd);
    getline(cin, cmd);
    string str = cmd.substr(36, cmd.length() - 37);

    vector<string> res;
    if (str == "") return res;
    string delim = ",";
    char *strs = new char[str.length() + 1];
    strcpy(strs, str.c_str());
    char *d = new char[delim.length() + 1];
    strcpy(d, delim.c_str());

    char *p = strtok(strs, d);
    while (p) {
        string s = p;
        res.push_back(s);
        p = strtok(NULL, d);
    }
    return res;
}
string build(int id, vector<string> &strs) {
    string res = "";
    for (string str : strs) {
        int dat = Data[id][vis[str]];
        if (res != "") res += ",";
        res += to_string(dat);
    }
    return res;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; ++i)
        cin >> title[i];
    for (int i = 1; i <= n; ++i) vis[title[i]] = i;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            cin >> Data[i][j];
    //
    vector<string> id = split_cmd();
    map<string, int> ans;
    for (int i = 1; i <= n; ++i)
        ans[build(i, id)]++;
    //output
    cout << ans.size() << endl;
    for (auto x : ans)
        cout << x.second << " ";
    return 0;
}
posted @ 2022-02-04 22:10  cyhforlight  阅读(26)  评论(0编辑  收藏  举报