【解题报告】Codeforces Round #380 (Div. 2, Rated, Based on Technocup 2017 - Elimination Round 2)

题目链接


A. Interview with Oleg(Codeforces 738A)

思路

因为本题的数据规模较小,所以直接预处理出所有的模式串(ogo, ogogo, …)暴力匹配即可。

代码

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

int n, cur;
string s, a, b, c, t;
vector <string> v;

int main() {
//  freopen("data.txt", "r", stdin);
    s = "ogo";
    v.push_back(s);
    // 预处理出所有可能的模式串
    for(int i = 1; i < 100; i++) {
        s = v[i-1];
        v.push_back(s + "go");
    }
    cin >> n >> s;
    while(cur < s.size()) {
        // 枚举所有可能的模式串
        for(int i = v.size() - 1; i >= 0; i--) {
            t = v[i];
            b = s.substr(cur, t.size());
            // 若成功匹配
            if(b == t) {
                a = s.substr(0, cur);
                b = "***";
                c = s.substr(cur + t.size(), s.size() - cur - t.size());
                // 构造新串
                s = a + b + c;
                cur += 2;
                break;
            }
        }
        cur++;
    }
    cout << s << endl;
    return 0;
}

思路

看了别人的代码后才知道C++11已经支持正则表达式了,于是在了解正则表达式的情况下,本题就变得非常简单了。

但是需要注意的是,G++ 4.8是不支持正则表达式的(CF的G++ 5.2是支持的)。

代码

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

string s;

int main() {
//    freopen("data.txt", "r", stdin);
    cin >> s >> s;
    cout << regex_replace(s, regex("o(go)+"), "***");
}

B. Spotlights(Codeforces 738B)

思路

首先题意中需要注意的是,即使在同一个格子中,方向不用也算是不同的good position。

用暴力的方法来做的话 O(nm(n+m)) 的复杂度我们承受不起,于是想办法优化。

如果对每个值为 0 的格子我们都能在 O(1) 的复杂度内知道它的上下左右方向上是否有值为 1 的格子,那么问题就解决了。如果题目给不是矩阵而是数组的话,就可以用处理前缀和的办法( a[i]+=a[i1],for each i )来得到该格子左右两边格子的值之和。现在给出了矩阵,那么我们就对每行,每列处理前缀和就好了。这样复杂度是 O(nm)

代码

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

const int maxn = 1010;
int n, m, ans;
int a[maxn][maxn], s1[maxn][maxn], s2[maxn][maxn];

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            scanf("%d", &a[i][j]);
        }
    }
    // 对每列预处理前缀和
    for(int j = 1; j <= m; j++) {
        for(int i = 1; i <= n; i++) {
            s1[j][i] = a[i][j] + s1[j][i-1];
        }
    }
    // 对每行预处理前缀和
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            s2[i][j] = a[i][j] + s2[i][j-1];
        }
    }
    // 遍历格子的同时求出答案
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(a[i][j] == 1) {
                continue;
            }
            if(s1[j][i-1] > 0) {
                ans++;
            }
            if(s1[j][n] - s1[j][i] > 0) {
                ans++;
            }
            if(s2[i][j-1] > 0) {
                ans++;
            }
            if(s2[i][m] - s2[i][j] > 0) {
                ans++;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

思路

实际上还有更简单的方法。我们可以用逆向思维将问题转化成,求“每个演员对照明灯的贡献之和”。也就是我们可以用演员去“看”照明灯,能看见照明灯的方向就对答案有贡献。

  • 首先讨论演员向左或向上看见探照灯的情况。对于一个演员,他的左方和上方的到离他最近的演员之前的所有空格都能被他看到。于是我们就需要对一个演员的位置,迅速知道他的左方和上方的离他最近的演员的位置。
  • 其次讨论演员向右或向下看见探照灯的情况,对于一个空格,若它的左方和上方有演员,那么它一定能被演员看到。

现在我们已经将问题转化成对一个格子的左方和上方进行询问的问题了。那么我们可以从左上开始扫描矩阵,同时维护每行的最后扫描到的演员所在的列号C和每列的最后扫描到的演员所在的行号R。按照上面的分析判断即可。

代码

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

const int maxn = 1010;
int n, m, C, a, ans, R[maxn];

int main() {
//    freopen("data.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        C = 0;
        for(int j = 1; j <= m; j++) {
            scanf("%d", &a);
            // 计算演员向左方,上方看能看见的空位
            if(a == 1) {
                ans += i - R[j] - 1;
                ans += j - C - 1;
                R[j] = i;
                C = j;
            }
            // 计算被右方,下方的演员看见的情况
            else {
                ans += (R[j] > 0);
                ans += (C > 0);
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

C. Road to Cinema(Codeforces 738C)

思路

首先我们应该发现,在道路信息和时间限制确定的情况下,车能否准时到终点同且仅同车的油量有关。显然是油量越大就越能够顺利到达终点。所以我们可以用二分查找的方法找到使得车辆准时到达终点的最小油量,然后所有油量大于这个值得车中价格最小的车的价格就是我们要求的。

接下来,问题就转化成如何二分查找,也就是如何根据给定油量判断是否能够到达终点。因为中途有加油站,我们可以把问题进一步分解成求某段距离(两个加油站间,距离为 D ),在给定油量 F 的情况下,求汽车最小行驶时间 T

因为接下来的计算似乎会涉及运动学的计算,所以我们先按照惯例把两个档位的速度和耗油率计算出来:

  • 第一档,速率 v1=0.5km/min , 耗油率 u1=0.5L/min

    • 第二档,速率 v2=1km/min , 耗油率 u2=2L/min
    • 我们设在上述长为 D 的路程内用第一档行驶了 t1 分钟,用第二档行驶了 t2 分钟。那么似乎可以列出方程如下:

      D=v1t2+v2t2

      F=u1t1+u2t2

      可以求得 T=t1+t2=3DF 。那么这个式子是否总是成立呢?显然不是的,这个式子只有在路程为 D 和油耗恰好 F 的时候成立。因此我们需要考虑式子不成立的情况。

      • 当油量不足的情况下(用第一档都不足以跑完全程),路程是不能等于 D 的。此时就不能到终点了。
      • 当油量过量的情况下(用第二档跑完全程还剩下油),油耗是不能等于 F 的。此时耗时为 Dv2

      这个过程有点类似于化学方程式中物质少量与过量的计算。至此所有情况都分析完毕,问题也能得到圆满解决。

      代码

      #include <bits/stdc++.h>
      using namespace std;
      
      typedef pair <int, int> p;
      const int maxn = 2e5 + 5, INF = 1e9 + 5;
      int n, k, s, t, l, r, mid, ans, x[maxn];
      p car[maxn];
      
      // 判断是否能在油箱容量为f的情况下按时到达
      bool ok(int f) {
          int res = 0;
          // 枚举每段路,计算其可能的最短耗时
          for(int i = 1; i <= k + 1; i++) {
              int d = x[i] - x[i-1];
              if(f < d) {
                  return false;
              }
              if(f >= d + d) {
                  res += d;
                  continue;
              }
              res += d + d + d - f;
          }
          return res <= t;
      }
      
      int main() {
      //    freopen("data.txt", "r", stdin);
          ios::sync_with_stdio(false);
          cin.tie(0);
          cin >> n >> k >> s >> t;
          for(int i = 1; i <= n; i++) {
              cin >> x[1] >> x[2];
              car[i] = p(x[1], x[2]);
          }
          for(int i = 1; i <= k; i++) {
              cin >> x[i];
          }
          sort(x + 1, x + k + 1);
          x[k+1] = s;
          r = ans = INF;
          // 二分查找
          while(r - l > 1) {
              int mid = (l + r) >> 1;
              ok(mid) ? (r = mid) : (l = mid);
          }
          for(int i = 1; i <= n; i++) {
              if(car[i].second >= r) {
                  ans = min(ans, car[i].first);
              }
          }
          if(ans == INF) {
              cout << "-1" << endl;
          }
          else {
              cout << ans << endl;
          }
          return 0;
      }

      D. Sea Battle(Codeforces 738D)

      思路

      由题目给的 01 字符串,我们可以算出理论上可以存在的最多船的数量 Max 。并且我们的每发炮弹都能让 Max 减少 1 。为什么呢?例如 000000 ,船的大小为 3 ,我们对 3 号位置发射炮弹,就能让 Max 减少 1

      所以我们先将 01 字符串解析成一个数组,数组的元素 a[i] 代表字符串中第 i 个连续的 0 块共含有 a[i] 0 。然后我们能够求出 Max 。最少的发射方案显然是 Max 与船的数量之差再加上 1 发(必然能够击中的),即 Maxa+1

      最后,对于输出炮弹打击位置的方法,其实前面已经介绍过了,就是反复输出当前 0 块第 b 个位置。

      代码

      #include <bits/stdc++.h>
      using namespace std;
      
      const int maxn = 2e5 + 10;
      char s[maxn];
      int n, a, b, k, cnt, num, ans, head, len, tmp;
      vector <int> h, v;
      
      int main() {
      //  freopen("data.txt", "r", stdin);
          scanf("%d%d%d%d%s", &n, &a, &b, &k, s + 1);
          // 将01字符串解析成数字序列
          for(int i = 1; i <= n; i++) {
              if(s[i] == '0') {
                  if(s[i-1] != '0') {
                      h.push_back(i);
                  }
                  cnt++;
              }
              if(s[i] == '1') {
                  if(s[i-1] == '0') {
                      v.push_back(cnt);
                      cnt = 0;
                  }
              }
          }
          if(cnt > 0) {
              v.push_back(cnt);
          }
          // 计算当前水域最多可以停多少船
          for(int val : v) {
              num += val / b;
          }
          ans = num - a + 1;
          printf("%d\n", ans);
          // 输出发射位置
          for(int i = 0; i < v.size(); i++) {
              if(ans == 0) {
                  break;
              }
              head = h[i];
              len = v[i];
              tmp = min(ans, len / b);
              for(int j = 1; j <= tmp; j++) {
                  printf("%d ", head + b * j - 1);
              }
              ans -= tmp;
          }
          return 0;
      }

      思路

      也可以边解析 01 串边得到答案。

      代码

      #include <bits/stdc++.h>
      using namespace std;
      
      const int maxn = 2e5 + 10;
      char s[maxn];
      int n, a, b, k, cnt, Max, ans;
      vector <int> vec;
      
      int main() {
      //    freopen("data.txt", "r", stdin);
          scanf("%d%d%d%d%s", &n, &a, &b, &k, s);
          for(int i = 0; i < n; i++) {
              cnt += s[i] == '1' ? -cnt : 1;
              if(cnt == b) {
                  vec.push_back(i + 1);
                  cnt = 0;
                  Max++;
              }
          }
          ans = Max - a + 1;
          printf("%d\n", ans);
          for(int i = 0; i < ans; i++) {
              printf("%d ", vec[i]);
          }
          return 0;
      }

      E. Subordinates(Codeforces 738E)

      思路

      首先因为每个上司可以有多个下属,而每个下属只能有一个上司,因此这个关系可以用树表达出来。

      其次,下属的上司(不一定是直接的)数量表示该下属在树的第几层(从 0 层开始)。所以如果输入的序列是正确的序列,那么将序列排序去重后必然能得到一个“ 0,1,2,3,... ”的序列。反之,如果中间有缺失的元素的话该序列就不是正确的序列。

      接下来的问题就是该怎么改正了。为了方便说明先假设 a[i] 为在树的 i 层的员工有多少个。于是:

      1. 看看 s 的层数是不是 0 ,如果不是就要把它改成 0

      2. 将其它的层数为 0 的员工加入待填队列(表示要将他们修改,但是修改的目的地未定)。
      3. 若有空位( a[i]==0 )的话就将待修改队列中的员工取出一个填上。
      4. 若需要填员工时,待填队列中没有员工了就将层数最大的员工加入待填队列。
      5. 其实我们不需要队列,由于员工都是一样的,所以只需一个计数器即可。然后我们扫描 a 数组,同时维护首尾指针,以便快速定位当前扫描到的位置(首指针)以及快速定位最大的层数(尾指针)。

        代码

        #include <bits/stdc++.h>
        using namespace std;
        
        const int maxn = 2e5 + 10;
        int n, s, a, ans, tmp, tail, cnt[maxn];
        
        int main() {
        //  freopen("data.txt", "r", stdin);
            scanf("%d%d", &n, &s);
            for(int i = 1; i <= n; i++) {
                scanf("%d", &a);
                if(i == s && a > 0) {
                    cnt[0]++;
                    ans++;
                }
                else {          
                    tail = max(tail, a);
                    cnt[a]++;
                }
            }
            if(cnt[0] > 1) {
                if(tail > 0) {
                    tmp += cnt[0] - 1;
                    ans += cnt[0] - 1;
                    cnt[0] = 1;
                }
                else {
                    ans += cnt[0] - 1;
                    printf("%d\n", ans);
                    return 0;
                }
            }
            for(int i = 1; i <= tail; i++) {
                if(cnt[i] == 0) {
                    if(tmp > 0) {
                        tmp--;
                        cnt[i]++;
                        continue;
                    }
                    ans++;
                    cnt[tail]--;
                    cnt[i]++;
                    while(cnt[tail] == 0) {
                        tail--;
                    }
                }
            }
            printf("%d\n", ans);
            return 0;
        }

        思路

        更简单的方法是,维护“当前已经完成安排员工数 num ”,在遍历数组的过程中,当 numn 时停止遍历。这样相当于自动在“待填队列”空的时候从数组尾部取出员工补上。

        代码

        #include <iostream>
        using namespace std;
        
        int n, s, x, ans, cnt = 1, a[200005];
        
        int main() {
            cin >> n >> s;
            for(int i = 1; i <= n; i++) {
                cin >> x;
                i == s ? ans += x > 0 : a[x]++;
            }
            for(int i = 1; cnt < n; i++) {
                cnt += a[i] ? a[i] : 1;
                ans += a[i] == 0;
            }
            cout << ans;
            return 0;
        }

        (其它题目略)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值