AtCoder Beginner Contest 334 A~F

A.Christmas Present(判断)

题意:

给出球拍和手套的价格,哪个贵买哪个,要求输出购买的物品。

分析:

判断输出即可。

代码:

#include 

using namespace std;
typedef long long LL;
const int N = 2e5 + 5e2;
void solve() {
    int B, G;
    cin >> B >> G;
    if (B > G) {
        cout << "Bat" << endl;
    } else {
        cout << "Glove" << endl;
    }
}

int main() {
    int Case = 1;
    while (Case--) {
        solve();
    }
    return 0;
}

B.Christmas Trees(数学)

题意:

有一条无限长的路,路上的 A A A种了一棵树,并以 A A A点为中心,每隔 k k k米均有一棵树,问 L ∼ R L \sim R LR之间共有多少棵树(包含边界)。

分析:

首先可以通过将区间左右端点减去 A A A,此时中心点就变为了原点。

然后考虑两种情况:

  • 左右边界均在原点一侧

  • 左右边界在原点两侧

如果左右边界在原点一侧,可以直接通过计算得到区间内的树的数量(左右边界均为负数时可以以原点为中心翻转)

如果左右边界在原点两侧,那么可以分别计算两边到原点之间有多少棵树。

代码:

#include 

using namespace std;
typedef long long LL;
const int N = 2e5 + 5e2;

void solve() {
    LL A, M, L, R;
    cin >> A >> M >> L >> R;
    L -= A, R -= A;
    LL ans = 0;
    if (L < 0 && R < 0) {
        L = -L, R = -R;
        swap(L, R);
    }
    if (L >= 0) {
        ans = R / M - L / M;
        if (L % M == 0) ans++;
    } else {
        ans = R / M + (-L) / M + 1;
    }
    cout << ans << endl;
}

int main() {
    int Case = 1;
    while (Case--) {
        solve();
    }
    return 0;
}

C.Socks 2(前/后缀和,枚举)

题意:

N N N双袜子,编号为 1 ∼ N 1 \sim N 1N,其中有 k k k双袜子丢失了,编号为 A 1 , A 2 , . . . , A k A_1, A_2, ..., A_k A1,A2,...,Ak,当穿上两只不同的袜子时,古怪值为这两只袜子编号之差的绝对值,问,如果将剩下的袜子两两配对(如果总数为奇数,可以丢掉一只袜子),最小的古怪值是多少。

分析:

对于没有发生丢失的袜子,不需要进行操作。

对于发生丢失的袜子,分为两种情况:

  • 丢失的袜子数量为偶数

  • 丢失的袜子数量为奇数

当丢失的袜子数量为偶数时,由于给出的袜子编号是有序的,那么将袜子按顺序进行匹配,古怪值为 ( A 2 − A 1 ) + . . . + ( A k − A k − 1 ) (A_2 - A_1) + ... + (A_k - A_{k - 1}) (A2A1)+...+(AkAk1)

当丢失的袜子数量为奇数时,由于无法知道丢掉哪只袜子是最优的,因此,可以对丢掉的袜子进行枚举,丢掉一只袜子后,剩余的袜子按偶数的情况进行计算,为了避免超时,可以预处理前缀和和后缀和,如果前后两段袜子数量恰好为奇数,那么就让前一段的最后一只袜子与后一段的第一只袜子匹配即可。

代码:

#include 

using namespace std;
typedef long long LL;
const int N = 2e5 + 5e2;

int a[N], L[N], R[N];

void solve() {
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= k; i++) cin >> a[i];
    int ans = 0;
    for (int i = 1; i <= k; i += 2) {
        ans += a[i + 1] - a[i];
    }
    L[2] = a[2] - a[1];
    for (int i = 4; i < k; i += 2) {
        L[i] = a[i] - a[i - 1] + L[i - 2];
    }
    for (int i = k - 1; i >= 1; i -= 2) {
        R[i] = a[i + 1] - a[i] + R[i + 2];
    }
    if (k % 2 == 1) {
        int res = 1e9;
        for (int i = 1; i <= k; i++) {
            if (i % 2 == 1) {
                if (i == 1) res = min(res, R[i + 1]);
                else res = min(res, L[i - 1] + R[i + 1]);
            } else {
                int num = L[i - 2] + R[i + 2] + a[i + 1] - a[i - 1];
                res = min(res, num);
            }
        }
        cout << res << endl;
    } else {
        cout << ans << endl;
    }
}

int main() {
    int Case = 1;
    while (Case--) {
        solve();
    }
    return 0;
}

D.Reindeer and Sleigh(前缀和,二分)

题意:

N N N个雪橇,第 i i i个雪橇需要 R i R_i Ri头驯鹿才能拉动。

Q Q Q个询问,每个询问会给出一个正整数 X X X,问有 X X X头驯鹿时最多可以拉动多少个雪橇。

分析:

既然想要拉动的雪橇数量尽可能多,那么应该从小到大的选择雪橇需要的驯鹿数量,因此可以先对需要的驯鹿数量排序,然后维护前缀和,之后的查询使用二分来进行。

代码:

#include 

using namespace std;
typedef long long LL;
const int N = 2e5 + 5e2;

LL R[N], pre[N];

void solve() {
    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        cin >> R[i];
    }
    sort(R + 1, R + n + 1);
    for (int i = 1; i <= n; i++) {
        pre[i] = pre[i - 1] + R[i];
    }
    while (q--) {
        LL x;
        cin >> x;
        int l = 0, r = n, ans = 0;
        while (l <= r) {
            int mid = l + r >> 1;
            if (pre[mid] <= x) {
                ans = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        cout << ans << endl;
    }
}

int main() {
    int Case = 1;
    while (Case--) {
        solve();
    }
    return 0;
}

E.Christmas Color Grid 1(DFS)

题意:

有一个 H H H W W W列的网格,其中每个单元格被涂成红色或绿色。

( i , j ) (i,j) (i,j)表示从上到下第 i i i行,从左到右第 j j j列的单元格。

单元格 ( i , j ) (i,j) (i,j)的颜色由字符 S i , j S_{i,j} Si,j表示, S i , j = " . " S_{i,j}="." Si,j="."表示单元格 ( i , j ) (i,j) (i,j)是红色的, S i , j = " . " S_{i,j}="." Si,j="."表示单元格 ( i , j ) (i,j) (i,j)是绿色的。

定义绿色连通分量是指顶点集是绿色单元格、边缘集为连接两个相邻绿色单元格的单元格的集合。当 ∣ x − x ′ ∣ + ∣ y − y ′ ∣ = 1 \lvert x−x^′\rvert + \lvert y−y^′\rvert=1 xx+yy=1时,单元格 ( x , y ) (x,y) (x,y) ( x ′ , y ′ ) (x',y') (x,y)被认为是相邻的。

选择一个红色单元格并随机地将其重新涂成绿色。计算重新涂色后网格中绿色连通分量数量的期望值,结果对 998244353 998244353 998244353取模。

分析:

对于每个红色方块,考虑将其重新涂成绿色后连通分量数量的差异。

固定一个红色方块 ( i , j ) (i,j) (i,j),假设有 x x x个绿色连通分量与单元格 ( i , j ) (i,j) (i,j)相邻。通过将 ( i , j ) (i,j) (i,j)涂成绿色,所有与 ( i , j ) (i,j) (i,j)相邻的绿色方块都与之相连,因此包含 ( i , j ) (i,j) (i,j)或者与之相邻的方块的绿色联通分量数量从 x x x变为 1 1 1。其他地方的连通分量数量不变,所以总数减少了 ( x − 1 ) (x-1) (x1)个。(当 x = 0 x=0 x=0 时,可以认为是减少了 − 1 -1 1个,即增加了 1 1 1个)。

由于网格大小只有 1000 × 1000 1000 \times 1000 1000×1000,因此可以枚举每个红色单元格,分别计算其涂色后的影响,结果求平均值即可,可以使用并查集或者 D F S DFS DFS进行维护。

代码:

#include 

using namespace std;
typedef long long LL;
const int MAXN = 1005;
const LL mod = 998244353;
char c[MAXN][MAXN];
LL a[MAXN][MAXN], cnt, n, m;

void dfs(int i, int j) {
    if (a[i][j] || c[i][j] != '#')
        return;
    a[i][j] = cnt;
    dfs(i + 1, j);
    dfs(i, j + 1);
    dfs(i - 1, j);
    dfs(i, j - 1);
}

int gt(int x, int y) {
    if (x > n || x < 1 || y > m || y < 1 || c[x][y] == '.')
        return 0;
    return a[x][y];
}

int get(int x, int y) {
    map<int, int> mp;
    if (gt(x + 1, y))
        mp[gt(x + 1, y)] = 1;
    if (gt(x, y + 1))
        mp[gt(x, y + 1)] = 1;
    if (gt(x - 1, y))
        mp[gt(x - 1, y)] = 1;
    if (gt(x, y - 1))
        mp[gt(x, y - 1)] = 1;
    return 1 - mp.size();
}

void exgcd(LL a, LL b, LL &x, LL &y) {
    if (!b) {
        x = 1;
        y = 0;
    } else {
        exgcd(b, a % b, y, x);
        y -= a / b * x;
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> c[i][j];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (c[i][j] == '#' && !a[i][j]) {
                ++cnt, dfs(i, j);
            }
        }
    }
    LL p = 0, q = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (c[i][j] == '.') {
                q++;
                p += cnt + get(i, j);
                p %= mod;
            }
        }
    }
    LL k = __gcd(p, q);
    p /= k;
    q /= k;
    LL x, y;
    exgcd(q, mod, x, y);
    x = (x % mod + mod) % mod;
    cout << (x * p) % mod << endl;
    return 0;
}

F.Christmas Present 2(动态规划)

题意:

圣诞老人住在一个用平面坐标系 x y xy xy表示的小镇上,小镇有 N N N个孩子,编号为 1 1 1 N N N。圣诞老人的家坐标为 ( S X , S Y ) (S_X,S_Y) (SX,SY),孩子 i i i ( 1 ≤ i ≤ N ) (1\le i \le N) (1iN)的家在坐标 ( X i , Y i ) (X_i,Y_i) (Xi,Yi)

圣诞老人想按数字的顺序给 N N N个孩子每人送一份礼物。为了给孩子 i i i送礼物,圣诞老人必须带着至少一件礼物去拜访孩子 i i i的家。然而,圣诞老人一次最多只能携带 K K K个礼物,把身上的礼物送完后,他必须回到自己的家补充礼物(圣诞老人家里有足够的礼物)。

求出圣诞老人离开家,给所有 N N N个孩子送礼物,然后返回家的最小路程。

分析:

对于每个孩子 i i i,在给孩子 i i i送礼物之后和给孩子 ( i + 1 ) (i+1) (i+1)送礼物之前有两条可能的路径:

  • 直接从房子 i i i到房子 ( i + 1 ) (i +1) (i+1);
  • 或者从房子 i i i回到圣诞老人的房子,然后前往房子 i + 1 i+1 i+1

用序列 d i d_i di,表示从圣诞老人家走到第 i i i个点的距离, s u m ( i → j ) sum(i→j) sum(ij)表示从房子 i i i走到房子 j j j的距离。

采用动态规划对其进行求解。令 d p i dp_i dpi表示前 i i i个孩子走完的最小花费。枚举 j j j d p i = m i n [ d p j + d ( j + 1 ) + d ( i ) + s u m ( j + 1 → i ) ] dp_i=min[dp_j+d(j+1)+d(i)+sum(j+1→i)] dpi=min[dpj+d(j+1)+d(i)+sum(j+1i)]。因为圣诞老人最多可以携带 K K K个礼物,所以 ( i − j ≤ k ) (i−j≤k) (ijk)。可以使用单调队列或者线段树进行优化。

代码:

#include

using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 5;

LL a[MAXN];
double d[MAXN], dp[MAXN], s[MAXN], sum[MAXN];

struct edge {
    double x, y;
} ed[MAXN];

double solve(double a, double b, double c, double d) {
    return sqrt((a - c) * (a - c) + (b - d) * (b - d));
}

int main() {
    int n, c;
    double sx, sy;
    cin >> n >> c >> sx >> sy;
    for (int i = 1; i <= n; i++)
        cin >> ed[i].x >> ed[i].y;
    for (int i = 1; i <= n; i++)
        dp[i] = 0x3f3f3f3f;
    dp[0] = 0;
    ed[0].x = sx;
    ed[0].y = sy;
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + solve(ed[i - 1].x, ed[i - 1].y, ed[i].x, ed[i].y);
        d[i] = solve(sx, sy, ed[i].x, ed[i].y);
    }
    int hd, tl;
    hd = tl = 1;
    a[1] = 0;
    for (int i = 1; i <= n; i++) {
        while (hd <= tl && i - a[hd] > c)
            hd++;
        if (hd <= tl)
            dp[i] = dp[a[hd]] + d[i] + d[a[hd] + 1] + sum[i] - sum[a[hd] + 1];
        while (hd <= tl && dp[a[tl]] + d[a[tl] + 1] - sum[a[tl] + 1] > dp[i] + d[i + 1] - sum[i + 1])
            tl--;
        a[++tl] = i;
    }
    cout << fixed << setprecision(8) << dp[n] << endl;
    return 0;
}

学习交流


以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

AtCoder Beginner Contest 334 A~F_第1张图片

你可能感兴趣的:(AtCoder,算法,AtCoder,信息学奥林匹克,ACM-ICPC,OI)