2022牛客冬令营 第五场 题解

A题 疫苗小孩 (枚举,二分)

如果只打两针,那么我们直接枚举第二针位置,然后找到最适合的打第一针的位置。

如果打三针,那么也是差不多流程,也就是在 pos - k 和 pos + k 附近找。

有点逆天,直接贴代码(set 真是好东西):

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1000010;
int n;
char str[N];
LL k, w, q;
LL calc(int x, int y) {
    return w - q * abs(k - (y - x));
}
set<int> s;
int get_front(int x) {
    auto a = s.lower_bound(x);
    if (a == s.begin()) return *a;
    else {
        auto b = a;
        b--;
        return abs(x - *a) < abs(x - *b) ? *a : *b;
    }
}
int get_back (int x) {
    auto a = s.lower_bound(x);
    if (a == s.end()) return *--s.end();
    else {
        auto b = a;
        b--;
        return abs(x - *a) < abs(x - *b) ? *a : *b;
    }
}
int main()
{
    //read
    scanf("%d%s", &n, str + 1);
    scanf("%lld%lld%lld", &k, &w, &q);
    //init
    for (int i = 1; i <= n; ++i)
        if (str[i] == '0') s.insert(i);
    //
    LL ans = 0;
    for (int pos : s) {
        int a = get_front(pos - k);
        int b = get_back (pos + k);
        if (a == pos) continue;
        ans = max(ans, calc(a, pos));
        ans = max(ans, calc(a, pos) + calc(pos, b));
    }
    cout << ans;
    return 0;
}

C题 战棋小孩 (贪心,二进制枚举)

考虑到 \(n\) 的范围,不难猜测有一个 \(O(2^n)\) 的操作,估计这个就是礼遇的枚举了。

抛开礼遇不谈,我们尝试看一下怎使得最后结果最大:显然按照加分从大到小来排序了(我第一遍以为那个第 \(i\) 局的上榜分数是跟着每局游戏走的,想了半天都看不懂题解,逆天)

#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n, k, s;
int a[N], b[N], c[N], d[N], p[N];
int main()
{
    //read
    cin >> n >> k >> s;
    for (int i = 1; i <= n; i++)
        cin >> p[i];
    for (int i = 1; i <= n; i++) {
        int aa, bb, cc, dd;
        cin >> aa >> bb >> cc >> dd;
        a[i] = max(aa, bb), b[i] = max(cc, dd);
    }
    //solve
    int ans = 0;
    for (int j = 0; j < (1 << n); j++) {
        int cnt = 0, tt = j;
        for(int k = 0; k < n; ++k) {
            c[k + 1] = (j >> k) & 1;
            if (c[k + 1]) ++cnt;
        }
        if (cnt > k) continue;
        for (int i = 1; i <= n; i++) {
            d[i] = a[i];
            if (c[i]) d[i] = max(d[i], b[i]);
        }
        sort(d + 1, d + n + 1);
        reverse(d + 1, d + n + 1);
        int score = s;
        cnt = 0;
        for (int i = 1; i <= n; i++) {
            score += d[i];
            if (score >= p[i]) ++cnt;
        }
        ans = max(ans, cnt);
    }
    //output
    cout << ans;
    return 0;
}

D题 数位小孩 (数位DP/DFS枚举)

给定区间 \([l,r]\),问区间内有多少个数,在十进制下符合以下要求:

  1. 每相邻两个数位,和为素数
  2. 数位中有至少一个 1
  3. 没有前导 0

\(1\leq l\leq r \leq 10^{10}\)

这题有点超过了数位 DP 的范围了(我是这么感觉的),不过靠数感意识到在 \(10^{10}\) 的范围内可能也就不到一百万个符合要求的数(实际数量为 1220185),这意味着我们可以写一个爆搜来找出所有的数,然后一一看他们在不在给定的区间内即可。

(我也不知道为啥考场上调了半小时没调的出来,很烦

#include<bits/stdc++.h>
using namespace std;
#define LL long long
set<LL> vis;
bool isPrime(const int x) {
    static int pr[7] = {2, 3, 5, 7, 11, 13, 17};
    for (int i = 0; i < 7; ++i) if (x == pr[i]) return true;
    return false;
}
bool has1(LL x) {
    for (; x; x /= 10) if (x % 10 == 1) return true;
    return false;
}
void dfs(LL x) {
    if (x >= 1e9) return;
    for (int i = 0; i < 10; ++i)
        if (isPrime(x % 10 + i)) {
            LL v = x * 10 + i;
            if (has1(v)) vis.insert(v);
            dfs(v);
        }
}
//
int main()
{
    //init
    vis.insert(1);
    for (int i = 1; i < 10; ++i) dfs(i);
    //
    LL l, r;
    cin >> l >> r;
    LL ans = 0;
    for (LL x : vis)
        if (l <= x && x <= r) ++ans;
    cout << ans;
    return 0;
}

E题 复苏小孩 (矩阵,线段树)

给定一个三元组 \((x,y,z)\),你可以对其进行操作,你可以进行操作 1, 2, 3,分别意味着让其他两个数变为自己的一半,然后剩下一半分别第 i 个数。

现在有一个仅含 1, 2, 3 的长度为 \(n\) 的操作序列,可进行 \(m\) 次操作:

  1. 1 x y,将第 \(x\) 个数改为 \(y\)\(1\leq x\leq n, 1\leq y\leq 3\)
  2. 2 l r,求对于三元组 \((1,1,1)\) 进行区间 \([l,r]\) 上操作后得到的新三元组。(\(1\leq l\leq r \leq n\)

\(1\leq n,m\leq 10^5\),所有数对 \(998244353\) 取模。

我们可以将操作视为对三元组向量左乘一个矩阵,例如第一个操作就可以视为:

\[\begin{bmatrix} 1 & \frac{1}{2} & \frac{1}{2}\\ 0 & \frac{1}{2} & 0\\ 0 & 0 & \frac{1}{2} \end{bmatrix} \begin{bmatrix} 1\\1\\1 \end{bmatrix} = \begin{bmatrix} 2\\\frac{1}{2}\\\frac{1}{2} \end{bmatrix} \]

矩阵符合结合律,那么就变成了一个单点修改和区间查询的问题,我们可以线段树来处理,复杂度 \(O(3^3n\log n)\)

(注意,矩阵乘法不符合交换律,所以必须严格遵守乘法顺序,先右后左)

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 998244353, inv = 499122177;

struct Matrix {
    LL a[4][4];
    void init(LL x) {
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= 3; ++i) a[i][i] = x;
    }
    Matrix operator * (const Matrix &B) const {
        Matrix res;
        res.init(0);
        for (int i = 1; i <= 3; ++i)
            for (int j = 1; j <= 3; ++j)
                for (int k = 1; k <= 3; ++k)
                    res.a[i][j] = (res.a[i][j] + a[i][k] * B.a[k][j]) % mod;
        return res;
    }
};
Matrix STD[4];
const int N = 100010;
int n, m;
char str[N];
struct Node {
    int l, r;
    Matrix s;
} a[N << 2];
int mp[N];
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
inline void pushup(int x) {
    a[x].s = a[rs(x)].s * a[ls(x)].s;
}
void build(int x, int l, int r) {
    a[x].l = l, a[x].r = r;
    if (l == r) {
        mp[l] = x;
        a[x].s = STD[str[l] - '0'];
        return;
    }
    int mid = (l + r) >> 1;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    pushup(x);
}
Matrix query(int x, int l, int r) {
    if (l <= a[x].l && a[x].r <= r)
        return a[x].s;
    int mid = (a[x].l + a[x].r) >> 1;
    Matrix res;
    res.init(1);
    if (r >  mid) res = res * query(rs(x), l, r);
    if (l <= mid) res = res * query(ls(x), l, r);
    return res;
}
void change(int p, Matrix val) {
	int x = mp[p];
    a[x].s = val;
    while (x >>= 1) pushup(x);
}

int main()
{
    //STD
    for (int i = 1; i <= 3; ++i) {
        STD[i].init(inv);
        for (int j = 1; j <= 3; ++j)
            STD[i].a[i][j] = inv;
        STD[i].a[i][i] = 1;
    }
    //read & init
    scanf("%d%d%s", &n, &m, str + 1);
    build(1, 1, n);
    //query
    while (m--) {
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if (opt == 1) change(x, STD[y]);
        else {
            Matrix G = query(1, x, y);
            LL ans[4] = {0, 0, 0, 0};
            for (int i = 1; i <= 3; ++i)
                for (int j = 1; j <= 3; ++j)
                    ans[i] = (ans[i] + G.a[i][j]) % mod;
            for (int i = 1; i <= 3; ++i)
                printf("%lld ", ans[i]);
            puts("");
        }
    }
    return 0;
}

G题 163小孩 (暴力,签到)

给定大小为 1 到 13 的卡牌各 4 张,从中取出六张,问本质不同的选择方案有几种?

暴力求出答案为 18395 即可。

I题 兔崽小孩 (二分,前缀和)

我们对时刻做差后排个序,然后每次询问中二分到第一个大于 \(k\) 的位置,然后仅计算后面的值即可(求和可以用前缀和优化)。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1000010;
int n, q, t[N];
//
LL a[N], s[N];
bool solve(LL k, LL p) {
    int pos = upper_bound(a + 1, a + n, k) - a;
    int len = (n - 1) - pos + 1;
    return (s[n - 1] - s[pos - 1] - len * k) >= p;
}
int main()
{
    //read
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &t[i]);
    //init
    for (int i = 1; i < n; ++i)
        a[i] = t[i + 1] - t[i];
    sort(a + 1, a + n);
    for (int i = 1; i < n; ++i)
        s[i] = s[i - 1] + a[i];
    //query
    while (q--) {
        LL k, p;
        scanf("%lld%lld", &k, &p);
        puts(solve(k, p) ? "Yes" : "No");
    }
    return 0;
}

J题 三国小孩 (思维,签到)

假定一个简化版三国杀,有着以下四种卡牌:杀,闪,桃,决斗。

现在知道我们血量无限,有 \(n\) 张杀和 \(m\) 张决斗,对方有 \(k\) 张手牌,问我们是否必胜?

\(0\leq n,m,k\leq 10^9\)

对于决斗后互相比拼杀的情况,我们可以理解为我们使用了一张杀,对方使用了一张闪,用来简化情况。

再简化一下得到结论:我们每打出一张伤害牌,对方都必须再出一张进行抵消,那么我们直接比较 \(n+m\)\(k\) 的大小即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, m, k;
    cin >> n >> m >> k;
    puts(n + m > k ? "YES" : "NO");
    return 0;
}
posted @ 2022-02-22 20:13  cyhforlight  阅读(30)  评论(0编辑  收藏  举报