ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Educational Codeforces Round 125(CF 1657) 简要题解

2022-03-28 11:34:53  阅读:176  来源: 互联网

标签:Educational le int 题解 LL CF 括号 while include


CF1657A Integer Moves

题意

给一个点 \((x, y)\) 问从坐标系原点 \((0, 0)\) 出发,每一步走的长度只能是整数,走到 \((x, y)\) 至少要多少步
\(t(t \le 3000)\) 组数据,其中 \(x \le 50, y \le 50\) 且 \(x, y\) 均为整数

题解

\(x \le 50, y \le 50\),就算暴力 \(dp\) 也能过...
然而这是 div2 的 A
注意到 \(x, y\) 均为整数,那么对于一个点 \((x, y)\) 考虑路径 \((0, 0) -> (0, x) -> (x, y)\) 每一步都是整数
所以答案小于等于 \(2\)
暴力判断答案是否是 \(0\) 和 \(1\) 即可,复杂度 \(O(t)\)

代码

#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;

int main()
{
    int t, x, y;
    cin >> t;
    while(t--){
        cin >> x >> y;
        if(x == 0 && y == 0) puts("0");
        else{
            int u = sqrt(x * x + y * y);
            if(u  * u == x * x + y * y) puts("1");
            else puts("2");
        }
    }
    return 0;
}

CF1657B XY Sequence

题意

给定整数 \(n, B, x, y\), 你需要构造一个序列 \({a_0, a_1, a_2,...a_n}\) 满足

  1. \(a_0 = 0\)
  2. \(a_i \le B\)
  3. \(\forall 1 \le i \le n, a_i = a_{i - 1} + x \or a_i = a_{i - 1} - y\)

输出序列元素和的最大值
其中 \(n \le 2 \times 10^5\)

题解

显然考虑贪心,能加就加,不能加再减

代码

#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long LL;

int main()
{
    int t, x, y, n, b;
    cin >> t;
    while(t--){
        cin >> n >> b >> x >> y;
        LL cur = 0, ans = 0;
        for(int i = 1; i <= n; i++)
            if(cur + x <= b) cur += x, ans += cur;
            else cur -= y, ans += cur;
        cout << ans << endl;
    }
    return 0;
}

CF1657C Bracket Sequence Deletion

题意

给定长度为 \(n(n \le 5 \times 10^5)\) 的括号序列,每次删除最短的回文或合法的前缀括号序列(回文前缀要求长度大于1),问能删几次以及剩下几个括号

题解

std

每次删除时,考虑第一个括号

  1. 如果是左括号
    1. 第二个括号依然是左括号,回文,删
    2. 第二个括号是右括号,合法,删
  2. 如果是右括号,那前缀无论如何也不可能合法了,只能考虑回文
    显然,最短的回文一定以下一个右括号结尾

我的考场暴力解法

考虑暴力判断合法括号序列与回文括号序列
合法括号序列 : 栈
回文括号序列 : manacher
复杂度 \(O(n)\)

代码(std)

#include <bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        string s;
        cin >> n >> s;
        int l = 0;
        int cnt = 0;
        while (l + 1 < n) {
            if (s[l] == '(' || (s[l] == ')' && s[l + 1] == ')')) {
                l += 2;
            } else {
                int r = l + 1;
                while (r < n && s[r] != ')') {
                    ++r;
                }
                if (r == n) {
                    break;
                }
                l = r + 1;
            }
            ++cnt;
        }
        cout << cnt << ' ' << n - l << '\n';
    }
    
    return 0;
}

CF1657D For Gamers. By Gamers.

题意

Monocarp 要逐个击败 \(m(m \le 3 \times 10^5)\) 只怪物,第 \(i\) 只怪物的血量为 \(H_i\),攻击力为 \(D_i\)。
Monocarp 没有战斗力,但他有 \(C(C \le 10^6)\) 个金币 (与每一只怪物战斗前他都有 \(C\) 个金币),他可以用这些金币招募一个小队与怪物战斗
Monocarp 在与每一只怪物战斗前都会重新招募小队
Monocarp 可以招募的战士有 \(n(n \le 3 \times 10^5)\) 种类型,第 \(i\) 种类型的每个战士有 \(h_i\) 的血量,\(d_i\) 的伤害和 \(c_i\) 的费用
Monocarp 深知团队和谐的重要性,所以他招募的战士只能是同种类型的,并且他希望每一个战士都不会死
Monocarp 招募的小队能战胜怪物当且仅当他们杀死怪物的时间比怪物杀死小队的一个战士的时间短
Monocarp 想知道他杀死每个怪物至少要多少金币,如果这个数字大于 \(C\),请输出 \(-1\)

题解

如果一个小队总伤害为 \(d\), 一个战士的血量为 \(h\), 怪物伤害为 \(D\), 血量为 \(H\)。
那么小队能击败怪物等价于 \(\frac{H}{d} < \frac{h}{D}\)
也就是 \(hd > HD\),那么我们不妨称 \(h\) 与 \(d\) 的乘积为一个战士或怪物的战斗力
发现 \(C\) 只有 \(10^6\),考虑暴力算出 \(i\) 个金币能招募的最大战斗力是多少
先算出 \(i\) 个金币能招募的一个战士最大战斗力是多少
然后对于 \(i\) 个金币能招募的战斗力最大的战士,这种战士最多能招募 \(\frac{C}{i}\) 个,暴力枚举这些情况
复杂度就是

\[\sum_{i = 1}^C \frac{C}{i} \approx C \log C \]

然后对于每只怪物,二分查找即可
总复杂度 \(O(C \log C + m \log C)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 1000010;

LL n, m, c, mx[N], v[N];

inline LL read()
{
    LL x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

int main()
{
    n = read(), c = read();
    for(LL i = 1, u, k, w; i <= n; i++){
        w = read(), u = read(), k = read();
        u = u * k;
        v[w] = max(v[w], u);
    }
    for(int i = 1; i <= c; i++)
        for(int j = 1; j * i <= c; j++)
            mx[i * j] = max(mx[i * j], v[i] * j);

    for(int i = 1; i <= c; i++)
        mx[i] = max(mx[i], mx[i - 1]);
    m = read();
    while(m--){
        LL cur = read() * read();
        if(cur >= mx[c]){
            printf("-1 ");
            continue;
        }
        int l = 1, r = c, ans = 0;
        while(l <= r){
            int mid = (l + r) >> 1;
            if(mx[mid] > cur) ans = mid, r = mid - 1;
            else l = mid + 1;
        }
        printf("%d ", ans);
    }
    return 0;
}

CF1657E Star MST

题意

问有多少个 \(n\) 阶标号无向完全图满足

  1. 边权是 \(1\) 到 \(k\) 之间的整数
  2. 以 \(1\) 为根的花是该完全图的一棵最小生成树
    其中 \(n, k \le 250\)

题解

容易发现,以 \(1\) 为根的花是该完全图的一棵最小生成树当且仅当对于任意的 \(i,j > 1\),\(i, j\) 之间的边权不小于 \(1, i\) 和 \(1, j\) 之间的边权
否则将 \((i, j)\) 替换掉 \((1, i)\) 和 \((1, j)\) 中较大的边会缩小这棵生成树
考虑枚举 \((1, i)\) 的边,假设 \(A\) 是所有满足 \(a_i \le k\) 的序列 \(a_n\) 的集合
那么答案就是

\[\sum_{a \in A} \prod_{i = 1}^n \prod_{j = i + 1}^{n} (k - \max(a_i, a_j) + 1) \]

观察数据范围,考虑从乘法分配率的角度切入计数dp
有 \((k - b + 1)^c\) 这一项的序列 \(a\) 应该满足的条件是对于所有的无序二元组 \((i, j)\),满足 \(\max(a_i, a_j) = b\) 的二元组有 \(c\) 个
分析这个条件,我们发现如果一个序列中有 \(x\) 个元素等于 \(b\), \(y\) 个元素小于 \(b\),那么满足 \(\max(a_i, a_j) = b\) 的二元组的个数就是确定的,为 \(xy+\frac{x(x-1)}{2}\)
那很显然,我们的计数dp有一维是 \(b\),一维是 \(x\), 一维是 \(y\)
设 \(f_{i, j, l}\) 表示已经填完了 \(1\) 到 \(i\),\(i\) 填了 \(j\) 个,其他数填了 \(l\) 个
那么

\[f_{i, j, l} = (\sum_{p = 0}^{l} f_{i - 1, p, l - p}) * \binom{n - l}{j} * (k - i + 1)^{jl + \frac{j(j-1)}{2}} \]

简单维护一下就可以做到 \(O(n^2k)\)
但是我们可以发现,转移方程中所调用的 \(f\) 都满足后两维相加等于 \(l\)
所以我们把 \(j\) 和 \(l\) 合并为一维 \(j + l\), 转移时枚举填了几个 \(i\) 就可以减少空间复杂度
即设 \(f_{i, j}\) 表示已经填完了 \(1\) 到 \(i\),填了 \(j\) 个数,那么

\[f_{i, j} = \sum_{p = 0}^{j} f_{i - 1, j - p} * \binom{n - j + p}{p} * (k - i + 1)^{p(j-p) + \frac{p(p-1)}{2}} \]

复杂度 \(O(n^2k)\)

代码

注意下面这份代码没有预处理 \((k - b + 1)^c\),复杂度为 \(O(n^2k \log n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 260;
const LL p = 998244353;

LL n, k, c[N][N], f[N][N], ans;

LL ksm(LL v, LL tms)
{
    LL res = 1;
    for(; tms; tms >>= 1, v = v * v % p) if(tms & 1) res = res * v % p;
    return res;
}

int main()
{
    cin >> n >> k, n--;
    for(int i = 0; i <= n; i++) c[i][0] = c[i][i] = 1;

    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % p;

    for(int i = 0; i <= k; i++) f[0][i] = 1;

    for(int i = 1; i <= n; i++)
      for(int j = 1; j <= k; j++)
        for(int h = 0; h <= i; h++)
          f[i][j] = (f[i][j] + f[i - h][j - 1] * c[n + h - i][h] % p * ksm(k - j + 1, h * (h - 1) / 2 + h * (i - h)) % p) % p;

    printf("%lld\n", f[n][k]);
    return 0;
}

CF1657F Words on Tree

题意

给一棵 \(n\) 个点的树和 \(q\) 条路径,对于每条路径再给一个长度等于路径长度的字符串(仅包含小写英文字母)
要求你给每个点钦定一个字母,使得对于一条路径 \(u, v\) 和它对应的字符串 \(s\),满足 \(u\) 到 \(v\) 或 \(v\) 到 \(u\) 的路径上所有点的字母连起来是 \(s\)
判断是否可行
如果可以,输出一种方案
其中 \(n, q \le 4 \times 10^5\)

题解

简单的 2-SAT 问题
如果一个结点没有被任何路径覆盖,那它是什么字母都行
如果它被至少一条路径覆盖了,那他的字母就至多只有两种可能
同样的,对于一条路径,他的字符串在路径上要么是 \(u\) 到 \(v\),要么是 \(v\) 到 \(u\),也是两种可能
所以对于 \(n\) 个结点和 \(q\) 条路径分别建两个点代表他们的两种可能,建图跑 2-SAT 即可

代码

这题代码比较毒瘤,细节很多
如果一个路径的字符串是回文的,那么他就只有一种可能
如果一个点被一条路径正着覆盖和倒着覆盖时字母都是一样的,那他也只有一种可能

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

const int N = 400010;
const int M = 1600010;

int n, q, dfsx, top, scc,
    d[N], fa[N], path[N], dfn[M], low[M], st[M], ins[M], bel[M], pa[N];

char s[N], col[N][2], ans[N];

vector <int> to[N], e[M];

inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void dfs(int u)
{
    for(int i = 0; i < to[u].size(); i++){
        int v = to[u][i];
        if(v == fa[u]) continue;
        fa[v] = u, d[v] = d[u] + 1;
        dfs(v);
    }
}

int getnum(bool tree, int num, int val)
{
    if(tree) return num + val * n;
    else return n + n + num + val * q;
}

void addedge(int u, int val1, int v, int val2)
{
    e[getnum(1, u, val1)].push_back(getnum(0, v, val2 ^ 1));
    e[getnum(0, v, val2)].push_back(getnum(1, u, val1 ^ 1));
}

void getpath(int num, int u, int v, char *s, int len)
{
    bool flag = true;
    for(int i = 1; i <= len; i++)
    if(s[i] != s[len - i + 1]){
        pa[num] = flag = 0;
        break;
    }
    if(flag) pa[num] = 1;
    // 上面在判断是否回文
    int du = 1, dv = 0;
    while(u != v){
        if(d[u] > d[v]) path[du] = u, u = fa[u], du++;
        else path[len - dv] = v, v = fa[v], dv++;
    }
    path[du] = u;
    for(int i = 1; i <= len; i++){
        int u = path[i];
        if(!col[u][0])
            col[u][0] = s[i], col[u][1] = s[len - i + 1];
        if(col[u][0] == col[u][1]) col[u][1] = 0; // 如果正着和倒着字母相等,说明只有一种可能

        if(col[u][0] != s[i]) addedge(u, 0, num, 0);
        if(col[u][1] != s[i]) addedge(u, 1, num, 0);
        if(pa[num]){ //如果回文,说明只有一种可能
            e[getnum(0, num, 1)].push_back(getnum(0, num, 0));
            continue;
        }
        if(col[u][0] != s[len - i + 1]) addedge(u, 0, num, 1);
        if(col[u][1] != s[len - i + 1]) addedge(u, 1, num, 1);
    }
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++dfsx, st[++top] = u, ins[u] = 1;
    for(int i = 0; i < e[u].size(); i++){
        int v = e[u][i];
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u]){
        scc++;
        while(st[top] != u)
            bel[st[top]] = scc, ins[st[top]] = 0, top--;
        top--, bel[u] = scc, ins[u] = 0;
    }
}

int main()
{
    n = read(), q = read();
    for(int i = 1, u, v; i < n; i++){
        u = read(), v = read();
        to[u].push_back(v), to[v].push_back(u);
    }
    dfs(1);
    for(int i = 1, u, v; i <= q; i++){
        u = read(), v = read();
        scanf("%s", s + 1);
        getpath(i, u, v, s, strlen(s + 1));
    }

    for(int i = 1; i <= n + n + q + q; i++) if(!dfn[i]) tarjan(i);

    for(int i = 1; i <= n; i++)
        if(bel[i] == bel[i + n]) return puts("NO"), 0;

    puts("YES");
    for(int i = 1; i <= n; i++)
        if(!col[i][0]) putchar('a');
        else if(col[i][0] == col[i][1]) putchar(col[i][0]);
        else if(bel[i] < bel[i + n]) putchar(col[i][0]);// scc 编号越小越靠近 dfs 树的叶子,拓扑序也就越大
        else putchar(col[i][1]);
    puts("");
    return 0;
}

标签:Educational,le,int,题解,LL,CF,括号,while,include
来源: https://www.cnblogs.com/sgweo8yspace/p/16065868.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有