【解题报告】Codeforces Round #363 (Div. 2)

这篇博客是关于Codeforces Round #363 (Div. 2)的比赛解题报告,详细分析了四道题目A. Launch of Collider、B. One Bomb、C. Vacations和D. Fix a Tree的思路和解决方案。对于每道题目,博主都阐述了如何确定碰撞条件、如何枚举放炸弹位置、如何转换问题求主人公工作最大天数,以及如何构造连通树并消除多余圈的方法。

题目链接


A. Launch of Collider(Codefoeces 699A)

思路

因为所有粒子的速度都是相同的,所以两个粒子发生碰撞的条件是,两个粒子相邻且右方的粒子向左运动,左方的粒子向右运动。

代码

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

const int maxn = 2e5 + 10;
char s[maxn];
int n, ans, a[maxn];

int main() {
    scanf("%d%s", &n, s);
    for(int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    ans = INT_MAX;
    for(int i = 1; i < n; i++) {
        if(s[i] == 'L' && s[i-1] == 'R') {
            ans = min(ans, (a[i] - a[i-1]) / 2);
        }
    }
    if(ans == INT_MAX) {
        puts("-1");
    }
    else {
        printf("%d\n", ans);
    }
    return 0;
}

B. One Bomb(Codeforces 699B)

思路

因为直接算出放炸弹的位置不是很方便,所以想到枚举放炸弹的位置,然后用常数的复杂度(根据数据规模)判断将炸弹放在这个位置是否能将所有的墙炸毁。既然判断的复杂度被限制在常数,那么我们事先需要知道一些信息。为此我们用 r[i] 表示第 i 行的墙的数量,用 c[i] 表示第 i 列的墙的数量。当我们枚举炸弹的位置枚举到 i j 列的时候,只要看 tmp=r[i]+c[j] 是否等于墙的总数就可以知道是否能够一次性炸完所有的墙。注意,当 i j 列上有墙的时候我们要将 tmp 再减去 1 ,以免重复统计。

代码

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

const int maxn = 1010;
bool ok;
char G[maxn][maxn];
int n, m, x, y, cnt, tmp, r[maxn], c[maxn];

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i++) {
        scanf("%s", G[i]);
        for(int j = 0; j < m; j++) {
            if(G[i][j] == '*') {
                r[i]++;
                c[j]++;
                cnt++;
            }
        }
    }
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            tmp = r[i] + c[j] - (G[i][j] == '*');
            if(tmp == cnt) {
                ok = true;
                x = i + 1;
                y = j + 1;
            }
        }
    }
    if(ok == false) {
        puts("NO");
    }
    else {
        printf("YES\n%d %d\n", x, y);
    }
    return 0;
}

C. Vacations(Codeforces 699C)

思路

我们可以将这个问题转化为求主人公最多可以工作多少天(这样比较自然),如果用 m 来表示这个量,那么答案就是 nm 。如果我们能知道前i天(包括第 i 天)的在某个状态 s (表示主人公的选择)下主人公工作最大天数,那么根据状态 s 下第 i+1 天主人公的选择,我们就可以更新前 i+1 天主人公工作最大天数。因此我们定义状态 (i,j) ,表示前 i 天的所有情况都已经考虑,在第 i 天选择了 j (0:休息,1:比赛,2:运动)的状态,那么 d[i][j] 就是该状态下的主人公工作的最大天数。我们可以根据规则(相邻两天不能干相同的事儿,当天没有比赛就没法比赛,当天体育馆不开就没法运动)来用 d[i][0],d[i][1],d[i][2] 来更新 d[i+1][0],d[i+1][1],d[i+1][2] 。最后的答案是 d[n][0],d[n][1],d[n][2] 中的最大值。

代码

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

const int maxn = 110;
int n, m, a[maxn], d[maxn][3];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    // 枚举今天是哪天
    for(int i = 0; i < n; i++) {
    // 枚举今天做什么
        for(int j = 0; j < 3; j++) {
            // 枚举明天做什么
            for(int k = 0; k < 3; k++) {
                // 不符合规则的话枚举下一件事儿
                if(j && (j == k || !(a[i+1] & j))) {
                    continue;
                }
                // 用今天的最优值更新明天的最优值
                d[i+1][j] = max(d[i+1][j], d[i][k] + (j > 0));
            }
            m = max(m, d[i+1][j]);
        }
    }
    printf("%d\n", n - m);
    return 0;
}

D. Fix a Tree(Codeforces 699D)

思路

题给的图是 n 个点和 n 条边的图,在纸上把样例画一画就能看出这个图的每个连通分量都含有一个圈(可能是自环,重边或其它圈)。这样,所以既让图连通,又消去除了一个自环以外的其它圈的办法就是:指定一个自环作为根节点(如果没有自环就让某个圈上的某个点的某条边指向该点),将所有其它圈上的某条边的某端拆开,连接到这个根节点上。显然,假如连通分量的个数为 c 的话,修改 c1 条边就能使图变成树了。怎么实现呢?首先我们尝试指定一个修改图之前就带自环的点为根节点。然后对于每个没访问过的点 u ,沿边访问该点所在连通分量中的所有点,同时为访问过的点加上访问标记。一旦访问到了标记过的点 v ,就表示可能出现以下两种情况中的一种情况: v 是之前标记的点, v 是圈上的点。前者可以不用理会,后者又可以根据情况不同有两种处理方式:若之前成功指定到了根节点,那么这个点的某条边就要连接到根节点上,若之前没能指定根节点,那么这个点的某条边就要连接到自己,然后该点就被指定为根节点。最后在将u枚举完后输出重新连接的边就好了。

代码

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

const int maxn = 2e5 + 10;
int n, root, cur, ans, a[maxn], c[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    // 尝试指定根节点
    for(int i = 1; i <= n; i++) {
        if(a[i] == i) {
            root = i;
        }
    }
    // 访问没访问过的点
    for(int i = 1; i <= n; i++) {
        if(c[i] != 0) {
            continue;
        }
        // 从i出发,访问i所在的连通分量
        for(cur = i; c[cur] == 0; cur = a[cur]) {
            // 加上访问标记
            c[cur] = i;
        }
        // 若访问过的点是之前访问过的或该点就是根节点
        if(c[cur] != i || root == cur) {
            continue;
        }
        // 若之前没能指定根节点就指定cur为根节点
        root = root ? root : cur;
        // 将边重新连接
        a[cur] = root;
        ans++;
    }
    printf("%d\n", ans);
    for(int i = 1; i <= n; i++) {
        printf("%d ", a[i]);
    }
    return 0;
}

(其它题目略)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值