ATCoder Beginnner Contest 341 A~G

A.Print 341(模拟)

题意:

给定一个正整数 N N N,输出由 N N N0 ( N + 1 ) (N+1) (N+1)1交替组成的字符串。

分析:

按题意模拟即可

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int n;
    cin >> n;
    cout << 1;
    for (int i = 0; i < n; i++) {
        cout << "01";
    }
    cout << endl;
    return 0;
}

B.Foreign Exchange(贪心)

题意:

N N N个国家,编号为 1 1 1 N N N。对于每个 i = 1 , 2 , … , N i=1,2,…,N i=1,2,,N,高桥拥有 A i A_i Ai个第 i i i个国家的货币。高桥可以重复执行以下操作任意次数(可能为零):

  • 首先,在 1 1 1 N − 1 N-1 N1(含两端)之间选择一个整数 i i i。然后,如果他拥有至少 S i S_i Si个第 i i i个国家的货币,则他会执行以下操作一次:用 S i S_i Si个第 i i i国家的货币交换 T i T_i Ti个下一个国家 ( i + 1 ) (i+1) (i+1)的货币。

计算高桥最终可能拥有的第 N N N个国家货币数量的最大值。

分析:

按照题意,按照题目所给规则一国一国模拟兑换即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
LL a[N];

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 0; i < n - 1; i++) {
        int s, t;
        cin >> s >> t;
        a[i + 1] += a[i] / s * t;
    }
    cout << a[n - 1] << endl;
    return 0;
}

C.Takahashi Gets Lost(模拟)

题意:

有一个 H H H W W W列的网格。网格的每个单元格都是陆地或海洋,由长度为 W W W H H H个字符串 S 1 , S 2 , … , S H S_1,S2,…,S_H S1,S2,,SH表示。 ( i , j ) (i,j) (i,j)表示从上往下第 i i i行和从左往右第 j j j列的单元格,并且如果 S i S_i Si的第 j j j个字符是. ,则 ( i , j ) (i,j) (i,j)是陆地;如果该字符是#,则 ( i , j ) (i,j) (i,j)是海洋。

题目约束保证网格周边(即满足 i = 1 i=1 i=1 i = H i=H i=H j = 1 j=1 j=1 j = W j=W j=W中至少一项的单元格 ( i , j ) (i,j) (i,j))上所有单元都是海洋。

T a k a h a s h i Takahashi Takahashi的飞船在网格中某个单元格坠毁了。之后,他按照长度为 N N N的字符串 T T T中表示的指令移动 N N N次,其中包括 L L L R R R U U U D D D 。对于 i = 1 , 2 , … , N i=1,2,…,N i=1,2,,N T T T的第 i i i个字符描述如下:

  • L L L表示向左移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i , j − 1 ) (i,j−1) (i,j1)处;
  • R R R表示向右移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i , j + 1 ) (i,j+1) (i,j+1)处;
  • U U U表示向上移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i − 1 , j ) (i−1,j) (i1,j)处;
  • D D D表示向下移动一个单元格。也就是说,在进行此次移动之前若他在 ( i , j ) (i,j) (i,j),那么此次移动后他将到达 ( i + 1 , j ) (i+1,j) (i+1,j)处。

已知沿着路径的所有单元格(包括坠毁并且当前所在位置)都不会被淹没,输出可能成为其当前位置的单元格数目。

分析:

题目要求按指令移动时不会经过海洋,即不会经过#。枚举位置,模拟操作序列,判断是否会经过#即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 505;

char c[N][N];

int main() {
    int h, w, n;
    cin >> h >> w >> n;
    string t;
    cin >> t;
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            cin >> c[i][j];
        }
    }
    int ans = 0;
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            if (c[i][j] == '#')
                continue;
            int a = i, b = j;
            bool check = true;
            for (int k = 0; k < n; k++) {
                if (t[k] == 'L') {
                    if (b == 1) {
                        check = false;
                        break;
                    }
                    b -= 1;
                    if (c[a][b] == '#') {
                        check = false;
                        break;
                    }
                } else if (t[k] == 'U') {
                    if (a == 1) {
                        check = false;
                        break;
                    }
                    a -= 1;
                    if (c[a][b] == '#') {
                        check = false;
                        break;
                    }
                } else if (t[k] == 'R') {
                    if (b == w) {
                        check = false;
                        break;
                    }
                    b += 1;
                    if (c[a][b] == '#') {
                        check = false;
                        break;
                    }
                } else {
                    if (a == h) {
                        check = false;
                        break;
                    }
                    a += 1;
                    if (c[a][b] == '#') {
                        check = false;
                        break;
                    }
                }
            }
            if (check) {
                ans++;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

D.Only one of two(二分)

题意:

给定三个正整数 N N N M M M K K K。其中, N N N M M M不同。

输出第 K K K小的正整数,该正整数恰好可被 N N N M M M中的一个数整除。

分析:

先找到 n , m n,m n,m的最小公倍数 l l l,那么对于一个数 x x x,能被 n n n整除且小于等于 x x x的数的个数就是 [ x / n ] [x/n] [x/n],所以可以推出(因为可能同时能被 n , m n,m n,m整除,要删掉能被 l l l整除的数字个数)

[ x / n ] + [ x / m ] − 2 ∗ [ x / l ] > = k [x/n]+[x/m]-2*[x/l]>=k [x/n]+[x/m]2[x/l]>=k

使用二分来进行查找即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

LL gcd(LL a, LL b) {
    return b ? gcd(b, a % b) : a;
}

int main() {
    LL n, m, x, k;
    cin >> n >> m >> k;
    x = (n * m) / gcd(n, m);
    LL l = 0, r = (LL) 2e+20;
    LL mid, cnt;
    while ((l + 1) < r) {
        mid = (l + r) / 2;
        cnt = (mid / n) + (mid / m) - 2 * (mid / x);
        if (cnt < k)
            l = mid;
        else
            r = mid;
    }
    cout << r << endl;
    return 0;
}

E.Alternating String(线段树)

题意:

定义好字符串是由01组成的字符串,其中两个连续字符总是不同的。

给你一个长度为 N N N的字符串 S S S,由01组成。

给出 Q Q Q个查询,必须按顺序处理。
查询有两种类型:

  • 1 L R:翻转 S S S中第 L L L到第 R R R的每个字符。也就是说,对于每个满足 L ≤ i ≤ R L\leq i\leq R LiR的整数 i i i,如果 S S S中的第 i i i个字符是"1",则将其改为0,反之亦然。
  • 2 L R:假设 S ′ S' S是通过提取 S S S的第 L L L到第 R R R个字符(不改变顺序)得到的长度为 ( R − L + 1 ) (R-L+1) (RL+1)的字符串。如果 S ′ S' S是一个好字符串,则输出Yes,否则输出No

分析:

题目要求的好字符串必须是01交替出现。设数组 x i = ( s i ⊕ s i + 1 ) x_i=(s_i \oplus s_{i+1}) xi=(sisi+1),如果 ∑ i = l r − 1 x i = = r − l \sum\limits_{i=l}^{r-1}x_i==r-l i=lr1xi==rl,那说明 S S S [ l , r ] [l,r] [l,r]01交替的。对区间 [ l , r ] [l,r] [l,r]进行翻转操作,不难发现所有的 x i x_i xi都不会发生变化。发生变化的只有两个边界,即 x l − 1 x_{l-1} xl1 x r x_r xr,显然这两个值会变成它们相反的数。我们注意到操作一对该数组的影响是两个单点修改,操作二是一个区间查询,用线段树维护数组即可。

代码:

#include<bits/stdc++.h>

using namespace std;

template<typename T>
class settree {
public:
    vector<T> cnt;
    int n;

    settree(int _n) : n(_n) { cnt.resize(n); }

    void modify(int x, T v) {
        while (x < n) {
            cnt[x] += v;
            x |= (x + 1);
        }
    }

    T get(int x) {
        T v{};
        while (x >= 0) {
            v += cnt[x];
            x = (x & (x + 1)) - 1;
        }
        return v;
    }

    T sum(int l, int r) {
        T tot = get(r);
        if (l != 0) {
            tot -= get(l - 1);
        }
        return tot;
    }
};

int main() {
    int n, q;
    cin >> n >> q;
    string s;
    cin >> s;
    settree<int> sum(n - 1);
    for (int i = 0; i < n - 1; i++) {
        sum.modify(i, s[i] != s[i + 1]);
    }
    while (q--) {
        int qu, l, r;
        cin >> qu >> l >> r;
        if (qu == 1) {
            --l, --r;
            if (l != 0) {
                sum.modify(l - 1, sum.sum(l - 1, l - 1) == 1 ? -1 : 1);
            }
            if (r != (n - 1)) {
                sum.modify(r, sum.sum(r, r) == 1 ? -1 : 1);
            }
        } else {
            --l, --r;
            if (sum.sum(l, r - 1) == (r - l))
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
    return 0;
}

F.Breakdown(01背包)

题意:

给你一个由 N N N个顶点和 M M M条边组成的简单无向图。对于 i = 1 , 2 , … , M i=1,2,\ldots,M i=1,2,,M来说,第 i i i条边连接顶点 u i u_i ui v i v_i vi。另外,对于 i = 1 , 2 , … , N i=1,2,\ldots,N i=1,2,,N来说,顶点 i i i被赋值为正整数 W i W_i Wi,并且有 A i A_i Ai个碎片放在上面。

只要图上还有碎片,就重复下面的操作:

  • 首先,从图形中选择一个点 x x x并移除上面的一个碎片。
  • 选择与 x x x相邻的顶点集合 S S S(可能为空),即 ∑ y ∈ S W y < W x \sum\limits_{y\in S}W_y\lt W_x ySWy<Wx,并在 S S S中的每个顶点上放置一个碎片。

输出此操作的最大次数。

题目保证无论如何操作,在有限次迭代后,图形上将没有棋子。

分析:

注意到操作里存在方向性即碎片总是往点权小的点跑。可以先求权值小的点,考虑权值大的点时,考虑放置碎片的点的答案都已经求出,因此可以求出该点的答案。

按点权小到大的顺序求解,假设 d p i dp_i dpi表示点 i i i上有一个碎片带来的最大操作次数,考虑求解当前点 d p i dp_i dpi时,就是考虑其 w y ≤ w i w_y \le w_i wywi的所有 y y y。选哪些 y y y,其 ∑ d p y \sum dp_y dpy最大。

可以发现这样考虑本题即为01背包问题,因此就按照点权小到大的顺序求解 n n n个01背包问题即可。

最后答案就是 ∑ ( d p i × a i ) \sum (dp_i \times a_i) (dpi×ai)

代码:

 #include<bits/stdc++.h>

typedef long long LL;
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edge(n);
    int u, v;
    for (int i = 0; i < m; i++) {
        cin >> u >> v;
        u--, v--;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    vector<int> w(n);
    for (auto &x: w)
        cin >> x;
    vector<int> a(n);
    for (auto &x: a)
        cin >> x;
    vector<int> id(n);
    iota(id.begin(), id.end(), 0);
    sort(id.begin(), id.end(), [&](int x, int y) { return w[x] < w[y]; });
    vector<int> cost(n);
    LL ans = 0;
    for (auto i: id) {
        vector<array<int, 2>> goods;
        for (auto j: edge[i]) {
            if (w[j] < w[i])
                goods.push_back({w[j], cost[j]});
        }
        if (goods.empty()) {
            cost[i] = 1;
        } else {
            vector<int> dp(w[i], 0);
            for (auto &[x, y]: goods) {
                for (int j = w[i] - 1; j >= x; j--) {
                    dp[j] = max(dp[j], dp[j - x] + y);
                }
            }
            cost[i] = 1 + *max_element(dp.begin(), dp.end());
        }
        ans += 1LL * a[i] * cost[i];
    }
    cout << ans << endl;
    return 0;
}

G.Highest Ratio(计算几何)

题意:

给你一个长度为 N N N的序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\ldots,A_N) A=(A1,A2,,AN)
求每个 k = 1 , 2 , … , N k=1,2,\ldots,N k=1,2,,N的解:

  • 当选择一个整数 r r r使得 k ≤ r ≤ N k\leq r\leq N krN时,求序列 A A A中第 k k k到第 r r r项的最大平均值。

序列 A A A的第 k k k到第 r r r项的平均值定义为 1 r − k + 1 ∑ i = k r A i \frac{1}{r-k+1}\displaystyle\sum\limits_{i=k}^r A_i rk+11i=krAi

分析:

考虑二维平面上编号为 0 , 1 , 2 , … , N 0,1,2,\ldots,N 0,1,2,,N ( N + 1 ) (N+1) (N+1)个点。

0 0 0的坐标为 ( x 0 , y 0 ) = ( 0 , 0 ) (x_0,y_0)=(0,0) (x0,y0)=(0,0),点 i i i的坐标为 ( x i , y i ) = ( i , A 1 + A 2 + ⋯ + A i ) (x_i,y_i)=(i,A_1+A_2+\cdots +A_i) (xi,yi)=(i,A1+A2++Ai)
那么 A L , A L + 1 , … , A R A_L,A_{L+1},\ldots,A_R AL,AL+1,,AR的平均值就表示为点 L − 1 {L-1} L1和点 R R R的斜率。

对于固定的 k k k ( 1 ≤ k ≤ N ) (1\leq k\leq N) (1kN),考虑如何求出序列 A A A中第 k k k到第 r r r项的最大平均值。
取点 k − 1 , k , k + 1 , … , N {k-1},k,k+1,\ldots,N k1,k,k+1,,N中的凸包。如果点 k − 1 , k , k + 1 , … , N {k-1},k,k+1,\ldots,N k1,k,k+1,,N是共线的,那么是 A k = A k + 1 = ⋯   A N A_k=A_{k+1}=\cdots~A_N Ak=Ak+1= AN,所以平均值是 A k A_k Ak。凸包是一个面积为正的区域。点 k − 1 {k-1} k1是唯一一个坐标最小为 x x x的点,位于凸包的边界上。从点 ( k − 1 ) (k-1) (k1)顺时针绕边界一周后,如果下一个点是 p p p,那么答案就是 y p − y k − 1 x p − x k − 1 \frac{y_p-y_{k-1}}{x_p-x_{k-1}} xpxk1ypyk1。这是因为,从点 ( k − 1 ) (k-1) (k1)逆时针时,如果下一个点是 q q q,那么对于构成凸包的点 i i i ( k ≤ i ≤ N ) (k\leq i\leq N ) (kiN),答案是 y p − y k − 1 x p − x k − 1 \frac{y_p-y_{k-1}}{x_p-x_{k-1}} xpxk1ypyk1。根据凸包的定义,经过点 i i i ( k − 1 ) (k-1) (k1)的直线的斜率至少是点 q q q和点 ( k − 1 ) (k-1) (k1)的斜率,最多是点 p p p ( k − 1 ) (k-1) (k1)的斜率。我们所求的值相当于连接点 i i i ( k ≤ i ≤ N ) (k\leq i\leq N) (kiN)和点 ( k − 1 ) (k-1) (k1)的直线的最大斜率。所以答案是 y p − y k − 1 x p − x k − 1 \frac{y_p-y_{k-1}}{x_p-x_{k-1}} xpxk1ypyk1,也就是连接点 p p p ( k − 1 ) (k-1) (k1)的直线 ( k − 1 ) (k-1) (k1)的斜率。

毕竟,对于每个点 k k k,只需考虑点 k − 1 , k + 1 , … , N {k-1},k+1,\ldots,N k1,k+1,,N的凸包,并找出从点 ( k − 1 ) (k-1) (k1)顺时针方向遍历凸包边界时遇到的下一个点即可;换句话说,就是构成上边界的点序列中点 ( k − 1 ) (k-1) (k1)的下一个点。

如果直接为每个 k k k寻找凸包,会超时。但是,在按此顺序添加点 N , N − 1 , … , 0 N,N-1,\ldots,0 N,N1,,0的同时求凸包上边界时,可以求出经过点 k − 1 {k-1} k1的直线的斜率,以及构成上边界的点序列中点 k − 1 {k-1} k1旁边的点。这样,就可以找到所有所需的值,其时间复杂度与只找到一次由点 0 , 1 , … , N 0,1,\ldots,N 0,1,,N组成的点集的凸包的时间复杂度相同。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 5;

LL pre[N], arr[N], q[N];

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
    }
    reverse(arr + 1, arr + n + 1);
    for (int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1] + arr[i];
    }
    vector<double> ans;
    int l = 0, r = -1;
    q[++r] = 0;
    for (int i = 1; i <= n; i++) {
        while (l < r && (pre[i] - pre[q[r]]) * (q[r] - q[r - 1]) <= (pre[q[r]] - pre[q[r - 1]]) * (i - q[r]))
            r--;
        ans.push_back(1.0 * (pre[i] - pre[q[r]]) / (i - q[r]));
        q[++r] = i;
    }
    for (auto it = ans.rbegin(); it != ans.rend(); ++it) {
        cout << fixed << setprecision(10) << *it << endl;
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值