【解题报告】Educational Codeforces Round 15

题目链接


A. Maximum Increase(Codeforces 702A)

思路

最暴力的思路是用两个指针枚举子串的首尾位置。但是这样的复杂度是 O(n2) 的,这样的算法承受不了本题的数据规模,说明这样枚举会出现许多重复。假设我们有一段序列 13,14,16,15,17 我们设置首指针为 1 ,当尾指针枚举到 4 的时候我们发现 15<16 因此尾指针没必要再枚举下去了,而首指针再枚举 2 3 也是没意义的,因为枚举出来的区间长度一定没有 [1,4] 区间大。因此索性设置下一个首指针为 4 ,而尾指针也可以直接设置为 5 ,然后继续枚举尾指针……重复这个过程直到尾指针枚举到头,每次修改头指针的时候更新要答案。(这个模仿虫子爬的方法就是《挑战程序设计竞赛》中的“尺取法”)

代码

#include 
using namespace std;

const int maxn = 1e5 + 10;
int n, ans, head, tail, a[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    ans = 1;
    head = 1;
    tail = 2;
    while(tail <= n) {
        // 如果尾指针能往下走就往下走
        if(a[tail] > a[tail-1]) {
            ans = max(ans, tail++ - head + 1);
        }
        // 否则重置首指针和尾指针
        else {
            head = tail++;
        }
    }
    printf("%d\n", ans);
    return 0;
}

B. Powers of Two(Codeforces 702B)

思路

根据复杂度的提示觉得这题应该是先枚举一个 i ,然后想办法瞬间知道 j ,使得 a[i]+a[j]=2x 。由于不同的 2x 只有 30 个(对最大的 a[i] 取对数可知),这使得我们可以在枚举 i 的情况下再枚举 2x ,当 a[i] 2x 都固定以后,就能在 a 中二分查找到满足条件的 j 了(前提是先对 a 排序)。

代码

#include 
using namespace std;

typedef long long ll;
const int maxn = 1e5 + 5, maxNum = 1e9;
int n, cnt, d, ub, lb, Pow[100], a[maxn];
ll ans;

int main() {
    Pow[0] = 1;
    for(cnt = 1; ; cnt++) {
        Pow[cnt] = Pow[cnt-1] << 1;
        if(Pow[cnt] >= maxNum) {
            break;
        }
    }
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= cnt; j++) {
            // d为a[i]固定后应该找的a[k]
            d = Pow[j] - a[i];
            if(d <= 0) {
                continue;
            }
            // 查找a[k]的下界
            lb = lower_bound(a + i + 1, a + n + 1, d) - a;
            // 查找a[k]的上界
            ub = upper_bound(a + i + 1, a + n + 1, d) - a;
            if(ub - lb > 0) {
                ans += ub - lb;
            }
        }
    }
    printf("%I64d\n", ans);
    return 0;
}

C. Cellular Network(Codeforces 702C)

思路

根据数据量猜想是二分查找半径(如果直接枚举,判断的话复杂度太高)。在二分查找中最多可以以 O(nlog(n)) 的复杂度判断给定的半径是否满足条件。我们来看看是否能在这样的限制内完成这个任务。首先,当我们确定了半径以后就能确定 m 个区间。只要我们能够判断 n 个点是否全部在这 m 个区间内就能够完成任务。
问题转化成是否能在规定的复杂度内判断 n 个点是否在 m 个区间内。接着,我们得到的区间是凌乱的,因此我们尝试对其排序看看是否可以简化问题,结果发现排完序后问题就明朗了。设排序完成后区间为 [a[0],b[0]][a[1],b[1]],...,[a[m1],b[m1]] (也就是用 a 数组存区间左端点,用 b 数组存区间右端点),设 city[i] 为第 i 个城市的坐标,我们枚举 i ,并依次检查这些城市是否在区间集合之外。然后,先二分查找到小于 city[i] 的最大的区间左端点 a[idx] ,然后在 b[0...idx1] 中找一个最大值 Max ,若 Max city[i] 还小的话就说明不存在区间包含 city[i] 了。而寻找区间最大值用 RMQ 算法即可(我用的是 ST 表)。这样判断半径是否满足条件的算法复杂度一共是 (n+m)log(m) ,再加上二分查找半径的复杂度,在 3 秒的条件下这题就得到圆满的解决了。
(另外在我朋友的提醒下知道这题有线性的算法,简单说是用两个指针分别指向城市和塔,然后移动指针并用离某个城市最近的塔距该城市的距离更新最小半径。这样跑出来是 62ms ,而我的是 702ms

代码

#include 
using namespace std;

typedef long long ll;
typedef pair  p;
const int maxn = 1e5 + 5, maxm = 1e5 + 5, maxNum = 1e9;
int n, m, l, r, mid, idx, city[maxn], tower[maxm];
p itv[maxm];
ll Max, a[maxm], b[maxm];

// 基于ST表的RMQ部分
struct ST {
    ll st[maxn][32];
    int log2[maxn];
    void initLog(int n) {
        log2[1] = 0;
        for(int i = 2; i <= n; i++) {
            log2[i] = log2[i-1];
            if((1 << log2[i] + 1) == i) {
                ++log2[i];
            }
        }
    }
    void init(int n, ll* a) {
        for(int i = n - 1; i >= 0; i--) {
            st[i][0] = a[i];
            for(int j = 1; (i + (1 << j) - 1) < n; j++) {
                st[i][j] = max(st[i][j-1], st[i+(1<1)][j-1]);
            }
        }
    }
    ll rmq(int l, int r) {
        int len = r - l + 1, k = log2[len];
        return max(st[l][k], st[r-(1<1][k]);
    }
}o;

// 判断给定半径是否满足条件
bool ok(ll R) {
    // 处理出m个区间区间
    for(int i = 1; i <= m; i++) {
        itv[i] = p(tower[i] - R, tower[i] + R);
    }
    // 对区间以区间左端点为关键字排序
    sort(itv + 1, itv + m + 1);
    for(int i = 1; i <= m; i++) {
        a[i-1] = itv[i].first;
        b[i-1] = itv[i].second;
    }
    // 初始化ST表
    o.init(m, b);
    // 对每个city[i]判断是否没有区间包含它
    for(int i = 1; i <= n; i++) {
        idx = lower_bound(a, a + m, (ll)city[i]) - a;
        if(a[idx] == city[i]) {
            continue;
        }
        if(idx == 0) {
            return false;
        }
        Max = o.rmq(0, idx - 1);
        if(Max < city[i]) {
            return false;
        }
    }
    return true;
}

int main() {
    o.initLog(maxm);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &city[i]);
    }
    for(int i = 1; i <= m; i++) {
        scanf("%d", &tower[i]);
    }
    // 二分查找满足条件的最小半径
    l = -1;
    r = 2 * maxNum;
    while(r - l > 1) {
        mid = (l + r) >> 1;
        ok(mid) ? r = mid : l = mid;
    }
    printf("%d\n", r);
    return 0;
}

D. Road to Post Office(Codeforces 702D)

思路

这种运动学的题目,无非是用模拟法或公式法解决。首先想到模拟法,应该先对问题分类,当 DK 时直接坐车显然更好。在其它情况下(先 D=K ,因为刚开始就在维修站,先坐一次车必然更好),我们应当判断相同距离 K 下,开车修车和直接走路谁用的时间更短,假设最短时间为 t ,那么在接下来的 D/K 个周期内每个周期都用这种方式会更好。最后剩下一段距离不足 K 的路程也是同样的道理,看看一次修车加一次开车的时间和直接走路的时间哪个更快,并选择更快的策略走到终点。

代码

#include 
using namespace std;

typedef long long ll;
ll D, K, A, B, T, t, ans;

int main() {
    cin >> D >> K >> A >> B >> T;
    ans += A * min(D, K);
    D = max(D - K, 0LL);
    // 计算一个周期内的最小运动时间
    t = min(A * K + T, B * K);
    // 将整数倍区间的路程走完
    ans += D / K * t;
    D %= K;
    // 将剩余的路程走完
    ans += min(A * D + T, B * D);
    cout << ans << endl;
    return 0;
}

(其它题目略)

你可能感兴趣的:(Codeforces)