Martian148's blog Martian148's blog
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档

Martian148

一只热爱文科的理科生
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档
  • codeforses 题解

  • atcoder 题解

    • AtCoder Beginner Contest 344 A-G 题解
      • A - Spoiler
        • Question
        • Solution
        • Code
      • B - Delimiter
        • Question
        • Code
      • C - A+B+C
        • Question
        • Solution
        • Code
      • D - String Bags
        • Question
        • Solution
        • Code
      • E - Insert or Erase
        • Question
        • Solution
        • Code
      • F - Earn to Advance
        • Question
        • Solution
        • Code
      • G - Points and Comparison
        • Question
        • Solution
        • Code
    • AtCoder Beginner Contest 345 A-F 题解
    • AtCoder Beginner Contest 346 A-G 题解
    • AtCoder Beginner Contest 347 A-G 题解
    • AtCoder Beginner Contest 353 A-G 题解
    • AtCoder Beginner Contest 359 A-G 题解
    • AtCoder Beginner Contest 366 A-F 题解
    • AtCoder Beginner Contest 369 A-F 题解
  • XCPC 题解

  • 校训练赛题解

  • 牛客题解

  • 蓝桥杯题解

  • 典题选讲

  • 杂题

  • 算法题解
  • atcoder 题解
martian148
2024-08-02
目录

AtCoder Beginner Contest 344 A-G 题解

AtCoder Beginner Contest 344 (opens new window)

# A - Spoiler

# Question

删除两个 | 之间的字符

# Solution

按照题意模拟即可

# Code

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s;
    cin >> s;
    string p1, p2;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == '|') break;
        p1.push_back(s[i]);
    }

    for (int i = s.size() - 1; i >= 0; i--) {
        if (s[i] == '|') break;
        p2.push_back(s[i]);
    }
    reverse(p2.begin(), p2.end());
    cout << p1 + p2 << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# B - Delimiter

# Question

倒叙输出输入的数

# Code

#include <bits/stdc++.h>
using namespace std;
int main() {
    vector<int> p;
    int x;
    while (cin >> x) p.push_back(x);
    reverse(p.begin(), p.end());
    for (auto &x : p) cout << x << '\n';
    return 0;
}
1
2
3
4
5
6
7
8
9
10

# C - A+B+C

# Question

给出三个集合 A,B,CA,B,CA,B,C,每次询问给出一个 XXX,问是否存在从 A,B,CA,B,CA,B,C 中挑出一个数字 a,b,ca,b,ca,b,c,使得 a+b+c=Xa+b+c=Xa+b+c=X

# Solution

由于集合大小很小,n3n^3n3 得预处理出 a+b+ca+b+ca+b+c 可能的所有值,最后 map 判断即可

# Code

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, m, l;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    cin >> m;
    vector<int> b(m);
    for (int i = 0; i < m; i++) cin >> b[i];
    cin >> l;
    vector<int> c(l);
    for (int i = 0; i < l; i++) cin >> c[i];
    
    unordered_map<int, int> mp;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            for (int k = 0; k < l; k++)
                mp[a[i] + b[j] + c[k]] = 1;
    
    int q; cin >> q;
    while (q--) {
        int x; cin >> x;
        cout << (mp.count(x) ? "Yes" : "No") << endl;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# D - String Bags

# Question

初始时有一个空字符串 SSS,此外有 1,2,⋯,N1,2,\cdots,N1,2,⋯,N 个袋子,每个袋子里面有装有一些字符串

袋子 iii 里包含 AiA_iAi​ 个字符串,对于每个袋子,你可以选择两种操作中的一种:

  • 支付 111 元,从袋子中选择一个字符串,将其连接到 SSS 的末尾
  • 什么也不做

给定一个字符串 TTT, 找到使最终 S=TS=TS=T 所需要的最小金额

# Solution

一眼背包问题,定义 dp[i][j]dp[i][j]dp[i][j] 表示枚举到第 iii 个袋子,前 i−1i - 1i−1 个袋子已经把 1∼j1\sim j1∼j 的字符串组成了的最小代价

对于每个袋子中的一个串 ppp,如果 p=T[j,j+p.size−1]p=T[j,j+p.size-1]p=T[j,j+p.size−1] ,那么代表可以往后添加 ppp 字串,于是转移方程为:

dp[i][j+p.size−1]=min⁡{dp[i−1][j]+1} dp[i][j+p.size-1]=\min\{dp[i-1][j]+1\} dp[i][j+p.size−1]=min{dp[i−1][j]+1}

# Code

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int main() {
    string T; cin >> T; T = " " + T;
    int n; cin >> n;
    vector<vector<string> > a(n + 1);
    for (int i = 1; i <= n; i++) {
        int m; cin >> m;
        for (int j = 0; j < m; j++) {
            string s; cin >> s;
            a[i].push_back(s);
        }
    }
    vector<vector<int> > dp(n + 1, vector<int>(T.size(), INF));
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < T.size(); j++) {
            for (int k = 0; k < a[i].size(); k++) {
                if (j + a[i][k].size() - 1 >= T.size()) continue;
                if (T.substr(j, a[i][k].size()) == a[i][k])
                    dp[i][j + a[i][k].size() - 1] = min(dp[i][j + a[i][k].size() - 1], dp[i - 1][j - 1] + 1);
            }
        }

        for (int j = 0; j < T.size(); j++)
            dp[i][j] = min(dp[i][j], dp[i - 1][j]);
    }
    int ans = INF;
    for (int i = 1; i <= n; i++)
        ans = min(ans, dp[i][T.size() - 1]);
    cout << (ans == INF ? -1 : ans) << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# E - Insert or Erase

# Question

给定长度为 NNN 的序列 A=(A1,A2,⋯,AN)A=(A_1,A_2,\cdots,A_N)A=(A1​,A2​,⋯,AN​)。序列 AAA 的元素各不相同

按照给定序列的顺序处理 QQQ 个查询:

  • 1 x y:在 AAA 中元素 xxx 之后插入 yyy
  • 2 x:从 AAA 中移除元素 xxx

输出处理完所有询问后的 AAA

# Solution

这是一个非常好的题,如果 NNN 比较小的话,就是一个链表的典题,在一个元素后插入一个元素,删除一个元素

但是本题的 NNN 非常大,如果能在短时间内找到链表的 L[x],R[x]L[x],R[x]L[x],R[x] 就能快速解题了

而 C++ 的 map 就很好的做到了这一点,使用 map 看成数组记录链表的 L[x],R[x]L[x],R[x]L[x],R[x] 之后就是链表的正常操作了

# Code

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int main() {
    ios::sync_with_stdio(false);
    int n; cin >> n;
    unordered_map<int,int> right, left;
    vector<int> a(n + 1, 0);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++) {
        if (i != 1) left[a[i]] = a[i - 1];
        if (i != n) right[a[i]] = a[i + 1];
    }
    left[a[1]] = -INF; right[a[n]] = INF;
    left[INF] = a[n]; right[-INF] = a[1];
    int q; cin >> q;
    while (q--) {
        int opt; cin >> opt;
        if (opt == 1) {
            int x, y; cin >> x >> y;
            const int L = x, R = right[x];
            left[y] = L; right[y] = R;
            right[L] = y; left[R] = y;
        }
        if (opt == 2) {
            int x; cin >> x;
            const int L = left[x], R = right[x];
            right[L] = R; left[R] = L;
            left.erase(x); right.erase(x);
        }
    }
    int x = right[-INF];
    while (x != INF) {
        cout << x << " ";
        x = right[x];
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# F - Earn to Advance

# Question

有一个 N×NN\times NN×N 的网格,初始处于 (1,1)(1,1)(1,1) ,金钱数为 000

当高桥处于 (i,j)(i,j)(i,j) 时,可以在依次行动中执行以下操作之一:

  • 留在原地并增加金钱 Pi,jP_{i,j}Pi,j​
  • 支付 Ri,jR_{i,j}Ri,j​ 移动到方格 (i,j+1)(i,j+1)(i,j+1)
  • 支付 Di,jD_{i,j}Di,j​ 移动到方格 (i+1,j)(i+1,j)(i+1,j)

问高桥移动到 (N,N)(N,N)(N,N) 的最小行动次数

# Solution

高桥移动的过程肯定是移动到一个点,然后攒够足够的钱,然后尽量把一个现有的钱用完去移动到下一个点,然后再在下一个点攒钱

因为如果有两个点 A,BA,BA,B ,有 PA<PBP_A < P_BPA​<PB​,那么高桥比起在 AAA 点攒钱,BBB 点攒钱肯定要划算一点,所以我们只需要在 AAA 点攒够足够的到 BBB 点的钱就好了

所以这样就可以定义 dpdpdp 了,定义 dp[x][y]dp[x][y]dp[x][y] 记录两个参数,第一个就是到 stepstepstep 即到 (x,y)(x,y)(x,y) 的最小步数,第二个是到 moneymoneymoney 即到 (x,y)(x,y)(x,y) 所剩下钱的最大值,其中要先保证步数最小,且需要在 (x,y)(x,y)(x,y) 这点出攒钱

我们只需要枚举上一次攒钱的地方 (x′,y′)(x',y')(x′,y′),需要额外的步数也就是是在 (x′,y′)(x',y')(x′,y′) 处攒钱的步数为 dis/Px′,y′dis/P_{x',y'}dis/Px′,y′​ ,其中 disdisdis 为 (x,y)(x,y)(x,y) 到 (x′,y′)(x',y')(x′,y′) 需要花费的钱的最小值

那么现在这个状态的步数和剩下的钱也就迎刃而解了

# Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,LL> pll;
const LL INF = 0x3f3f3f3f3f3f3f3f;

int main() {
    int n; cin >> n;
    vector<vector<LL> > P(n + 1, vector<LL>(n + 1)), D(n + 1, vector<LL>(n + 1)), R(n + 1, vector<LL>(n + 1));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> P[i][j];
    for (int i = 1; i <= n; i++)
        for (int j = 1; j < n; j++)
            cin >> R[i][j];
    for (int i = 1; i < n; i++)
        for (int j = 1; j <= n; j++)
            cin >> D[i][j];
    
    auto get_dis = [&](int x, int y) {
        vector<vector<LL> > dis(n + 1, vector<LL>(n + 1, INF));
        dis[x][y] = 0;
        for (int px = n; px >= 1; px--)
            for (int py = n; py >= 1; py--) {
                if (px < x) 
                    dis[px][py] = min(dis[px][py], dis[px + 1][py] + D[px][py]);
                if (py < y)
                    dis[px][py] = min(dis[px][py], dis[px][py + 1] + R[px][py]);
            }
        return dis;
    };

    vector<vector<pll> > dp(n + 1, vector<pll>(n + 1, {INF,INF}));
    dp[1][1] = {0,0};
    for (int x = 1; x <= n; x++)
        for (int y = 1; y <= n; y++) {
            auto dis = get_dis(x,y);
            for (int px = 1; px <= x; px++)
                for (int py = 1; py <= y; py++){
                    auto [p_step, p_money] = dp[px][py];
                    LL ex_step = (dis[px][py] - p_money + P[px][py] - 1) / P[px][py]; ex_step = max(ex_step, 0LL);
                    LL now_step = p_step + ex_step + (x - px) + (y - py);
                    LL now_money = p_money + ex_step * P[px][py] - dis[px][py];
                    if (now_step < dp[x][y].first || (now_step == dp[x][y].first && now_money > dp[x][y].second))
                        dp[x][y] = {now_step, now_money};
                }
        }
    cout << dp[n][n].first << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# G - Points and Comparison

# Question

在 xyxyxy 平面中有 NNN 个点 (Xi,Yi)(X_i,Y_i)(Xi​,Yi​)

给出了 QQQ 对整数 (Aj,Bj)(A_j,B_j)(Aj​,Bj​)

定义 f(Aj,Bj)f(A_j,B_j)f(Aj​,Bj​) 为满足 Yi≥Aj×Xi+BjY_i \ge A_j \times X_i + B_jYi​≥Aj​×Xi​+Bj​ 的 iii 的数量

求 ∑j=1Qf(Aj,Bj)\sum\limits_{j=1}^Q f(A_j,B_j)j=1∑Q​f(Aj​,Bj​)

# Solution

不妨设 k=A,b=Bk=A,b=Bk=A,b=B ,发现 (Aj,Bj)(A_j,B_j)(Aj​,Bj​) 其实表示的是一条直线,而 (Xi,Yi)(X_i,Y_i)(Xi​,Yi​) 表示的是平面上的一个点

观察 Yi≥kXi+bY_i \ge k X_i+bYi​≥kXi​+b,就是点在直线上方或是刚在在直线上

由于 k,bk,bk,b 是固定的,移项 Yi−kXi≥bY_i - kX_i \ge bYi​−kXi​≥b ,也就是说,如果 Yi−kXiY_i-kX_iYi​−kXi​ 是递增的,那么 bbb 就可以二分来找答案了

所以关键在于如何维护 Yi−kXiY_i-kX_iYi​−kXi​ 递增,我们把这样的排列叫做排列 AAA

先考虑极端情况 k=−∞k=-\inftyk=−∞ ,那么 pair<int,int> 正好满足 AAA 排序

考虑 AAA 排序上的两个点 (Xl,Yl)(X_l,Y_l)(Xl​,Yl​),(Xr,Yr)(X_r,Y_r)(Xr​,Yr​) ,对于一个给定的 kkk

  • 如果 Xl==XrX_l==X_rXl​==Xr​,则排序还满足 Yl,YrY_l,Y_rYl​,Yr​ ,也就是说,l,rl,rl,r 的位置不变
  • 如果 Yl−kXl>Yr−kXr⟹Yr−YlXr−Xl>kY_l-kX_l > Y_r-kX_r\Longrightarrow \frac{Y_r-Y_l}{X_r-X_l} > kYl​−kXl​>Yr​−kXr​⟹Xr​−Xl​Yr​−Yl​​>k 也就是说,l,rl,rl,r 两点组成的斜率大于了 kkk ,则交换 l,rl,rl,r

考虑到没两个点至多只需要交换一次,所以把询问按照 kkk 从小到大排序,这样子对于每对关系,如果交换了一次,后面总是满足 AAA 排列的规则的

对于相邻的点 (Xi,Yi)(X_i,Y_i)(Xi​,Yi​),(Xi+1,Yi+1)(X_{i+1},Y_{i+1})(Xi+1​,Yi+1​) 的斜率值放到优先队列里面

如果说对于某一个 kkk 满足 kkk 大于其中的某个斜率了,就把对应的两个点交换,然后继续维护优先队列

当满足了对于一个 kkk 的排列 AAA,就在 AAA 上面二分来找答案

对于时间复杂度分析,最多存在 N2N^2N2 对关系,所以交换操作最多操作 N2N^2N2 次,总时间复杂度为 O(Q(log⁡Q+log⁡N)+N2log⁡N)O(Q(\log Q+\log N)+N^2\log N)O(Q(logQ+logN)+N2logN)

# Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
typedef pair<LL,LL> pll;

const LL mod = (1ll << 31) - 1;

struct Line {
    LL up, dn;
    int pre, nxt; // 两者的编号
    Line(LL _up, LL _dn, int _pre, int _nxt) {
        if (dn < 0) up = -up, dn = -dn;
        up = _up, dn = _dn, pre = _pre, nxt = _nxt;
    }
    bool operator < (const Line &rhs) const {
        return up * rhs.dn > dn * rhs.up;
    }
    bool operator == (const Line &rhs) const {
        return up == rhs.up && dn == rhs.dn && pre == rhs.pre && nxt == rhs.nxt;
    }
};

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);

    int n; cin >> n;
    vector<pll> a(n + 1);
    for (int i = 1; i <= n; i++) {
        auto & [k, b] = a[i];
        cin >> k >> b;
    }
    sort (a.begin() + 1, a.end());
    
    int Q; cin >> Q;
    int G0, Ra, Rb; cin >> G0 >> Ra >> Rb;
    vector<pll> q(Q + 1); vector<LL> G(3 * Q + 1); G[0] = G0;
    for (int i = 1; i <= 3 * Q; i++) 
        G[i] = (48271 * G[i - 1]) % mod;
    for (int i = 1; i <= Q; i++) {
        auto &[A, B] = q[i];
        A = -Ra + (G[3 * i - 2] % (2 * Ra + 1));
        B = -Rb + ((G[3 * i - 1] * mod + G[3 * i]) % (2 * Rb + 1));
    }
    sort (q.begin() + 1, q.end());

    auto make_line = [&] (int i, int j) {
        return Line(a[j].second - a[i].second, a[j].first - a[i].first, i, j);
    };

    priority_queue<Line> s;
    for (int i = 1; i < n; i++)
        if (a[i].first < a[i + 1].first)
            s.push (make_line(i, i + 1));

    LL ans = 0;
    for (int i = 1; i <= Q; i++) {
        auto [k, b] = q[i];
        while (s.size() > 0) {   
            auto it = s.top();
            int dn = it.dn, up = it.up, pre = it.pre, nxt = it.nxt;
            if (!(make_line(pre, nxt) == it)) {s.pop(); continue;} // 说明这个线段已经被更新过了
            if (k * dn < up) break;  // 当 k > 两点的斜率时,交换
            s.pop(); swap(a[pre], a[nxt]);
            if (pre > 1 && a[pre - 1].first < a[pre].first) s.push(make_line(pre - 1, pre));
            if (nxt < n && a[nxt].first < a[nxt + 1].first) s.push(make_line(nxt, nxt + 1));
        }

        int l = 1, r = n;
        while (l <= r) {
            int mid = (l + r) >> 1;
            auto [X, Y] = a[mid];
            if ( -k * X + Y >= b) r = mid - 1;
            else l = mid + 1;
        }
        ans += n - r;
    }
    cout << ans << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
上次更新: 2025/04/08, 18:03:31
Codeforces Round 1016 (Div. 3) A-G 题解
AtCoder Beginner Contest 345 A-F 题解

← Codeforces Round 1016 (Div. 3) A-G 题解 AtCoder Beginner Contest 345 A-F 题解→

最近更新
01
Java基础语法
05-26
02
开发环境配置
05-26
03
pink 老师 JavaScript 学习笔记
05-26
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Martian148 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式