CSDN平台比赛第五期第四题:三而竭

CSDN平台比赛第五期第四题:三而竭

前言

机缘巧合之下我得知了CSDN举办的这个比赛。为了复习推研机试,就报名参赛准备练习一下。本来在时间截止时只拿了80分,以为没有名次了,不曾想这次比赛报名人少,我的名次还能拿到奖励,实属幸运。领取奖励需要撰写一篇比赛相关的博客,故作此文。

题面描述

一鼓作气再而衰三而竭。 小艺总是喜欢把任务分开做。 小艺接到一个任务,任务的总任务量是 n n n。 第一天小艺能完成 x x x份任务。 第二天能完成 x k \frac x k kx……第 t t t天能完成 x k t − 1 \frac x{k^{t-1}} kt1x。 小艺想知道自己第一天至少完成多少才能完成最后的任务。

关于数据范围,比赛结束后的题解并未提及,凭记忆应当是 1 ≤ n ≤ 1 0 9 1 \le n \le 10^9 1n109 k > 2 k > 2 k>2,且 n n n x x x k k k均为正整数。

思路分析

其实这个题目描述还是有点不清晰的,按最后的结果来看,实际上第 t t t天能完成的任务量应该是 ⌊ x k t − 1 ⌋ \lfloor \frac x{k^{t-1}}\rfloor kt1x

思考这个问题首先可以从一个数学结论下手: ⌊ x k t ⌋ = ⌊ ⌊ x k t − 1 ⌋ k ⌋ \lfloor \frac x{k^{t}}\rfloor=\lfloor \frac {\lfloor\frac x {k^{t-1}}\rfloor}{k}\rfloor ktx=kkt1x。要证明这个结论,可以设 ⌊ x k t ⌋ = a \lfloor \frac x{k^{t}}\rfloor=a ktx=a,则有 a k t ≤ x ≤ ( a + 1 ) k t − 1 ak^t\le x\le (a+1)k^t-1 aktx(a+1)kt1,所以 a k ≤ x k t − 1 ≤ ( a + 1 ) k − 1 k t − 1 ak\le\frac x{k^{t-1}}\le (a+1)k-\frac1{k^{t-1}} akkt1x(a+1)kkt11,有 a k ≤ ⌊ x k t − 1 ⌋ ≤ ( a + 1 ) k − 1 ak\le\lfloor\frac x{k^{t-1}}\rfloor\le (a+1)k-1 akkt1x(a+1)k1,那么自然能得出 ⌊ ⌊ x k t − 1 ⌋ k ⌋ = a = ⌊ x k t ⌋ \lfloor \frac {\lfloor\frac x {k^{t-1}}\rfloor}{k}\rfloor=a=\lfloor \frac x{k^{t}}\rfloor kkt1x=a=ktx。所以说我们可以利用这个性质不断展开 ⌊ x k t ⌋ \lfloor \frac x{k^{t}}\rfloor ktx,如果看做一个关于 t t t的数列(不妨记作 { c t } \{c_t\} {ct} { c t } = ⌊ x k t ⌋ \{c_t\}=\lfloor \frac x{k^{t}}\rfloor {ct}=ktx),后一项就能通过前一项导出( c t = ⌊ c t − 1 k ⌋ c_t=\lfloor\frac{c_{t-1}}{k}\rfloor ct=kct1),题目要求的是这个无限数列的和(不妨记作 S S S)要不小于给定值。

先给 x x x找一个大致区间,首先 x ≤ n x\le n xn,否则第一天就直接把所有任务完成了。其次由下取整的性质可以知道 n ≤ S ≤ x + x k + x k 2 + . . . = x × k k − 1 n\le S\le x+\frac x k+\frac x {k^2}+...=x\times\frac k {k-1} nSx+kx+k2x+...=x×k1k,故 x ≥ ( k − 1 ) n k x\ge\frac{(k-1)n}{k} xk(k1)n,这个夹的区间长度是 n k \frac n k kn,由题目的数据范围可知这个区间长度最大也就 1 0 9 2 \frac{10^9}{2} 2109,只要我们验证其间每个 x x x的值是否符合题目条件所需的算法复杂度不那么高,就完全足够支撑我们直接对这个区间进行遍历搜索(其实这个时候可以二分查找,但是反正遍历也能过,就没必要写二分了)。

要保证单次查询的复杂度足够低,可以用类似dp的思路,记录上一次搜索的状态,在下一次搜索中加以利用。由之前的提到的数学结论,我们有 c t = ⌊ c t − 1 k ⌋ c_t=\lfloor\frac{c_{t-1}}{k}\rfloor ct=kct1,也就是只要从某一项开始与上次搜索相同,那么后续的所有项(以及它们的和)也就完全与上一次搜索相同。由于 k ≥ 2 k\ge2 k2 x ≤ n ≤ 1 0 9 x\le n\le10^9 xn109,那么 c t c_t ct最多到 30 30 30项时就开始全为 0 0 0了,我们就可以开个 30 30 30项的数组,第 t t t个项记录 c t c_t ct的值与从 c t c_t ct开始所有项的和,那么每次计算就不用重复算出所有 c t c_t ct的值了——因为它们大多数都与上一次搜索相同。通过这个小技巧,如果 x x x从下界开始逐步加一进行遍历,那么每隔 k k k项才会更新一次 c 1 c_1 c1,每隔 k 2 k^2 k2项才会更新一次 c 2 c_2 c2,每隔 k 3 k^3 k3项才会更新一次 c 3 c_3 c3……,按期望来看每次对 x x x的搜索验算复杂度只有 1 + 1 k + 1 k 2 + 1 k 3 + . . . = k k − 1 ≤ 2 1+\frac{1}{k}+\frac{1}{k^2}+\frac{1}{k^3}+...=\frac{k}{k-1}\le2 1+k1+k21+k31+...=k1k2,完全能支撑我们的逐个遍历算法。

最终代码

这份代码是能通过比赛中本题的所有测例的:

// 请关闭中文输入法,用英文的字母和标点符号。
// 如果你想运行系统测试用例,请点击【执行代码】按钮,如果你想提交作答结果,请点击【提交】按钮,
// 注意:除答案外,请不要打印其他任何多余的字符,以免影响结果验证
// 本OJ系统是基于 OxCoder 技术开发,网址:www.oxcoder.com
// 模版代码提供基本的输入输出框架,可按个人代码习惯修改

#include <iostream>
#include <string>
#include <sstream>
#include <vector>

struct Info {
    int sum;
    int now;
};

int info_len = 30;
Info* info_array = new Info[info_len];

void renew(int index, int now, int k) {
    if (now == info_array[index].now) return;
    info_array[index].now = now;
    if (index < info_len - 1) {
        renew(index + 1, now / k, k);
        info_array[index].sum = info_array[index + 1].sum + now;
    } else {
        info_array[index].sum = now;
    }
}

int solution(std::vector<int>& vec){
    int n = vec[0], k = vec[1];
    int result = (k - 1) * n / k;
    info_array[0].now = result;
    for (int i = 1; i < info_len; ++i) {
        info_array[i].now = info_array[i - 1].now / k;
    }
    info_array[info_len - 1].sum = info_array[info_len - 1].now;
    for (int i = info_len - 2; i >= 0; --i) {
        info_array[i].sum = info_array[i].now + info_array[i + 1].sum;
    }
    for ( ; info_array[0].sum < n; renew(0, ++result, k));
    return result;
}

int main() {
    std::vector<int> vec;
    std::string line_0, token_0;
    getline(std::cin >> std::ws, line_0);
    std::stringstream tokens_0(line_0);
    while(std::getline(tokens_0, token_0, ' ')){
        vec.push_back(std::stoi(token_0));
    }
    int result = solution(vec);
    std::cout << result << std::endl;
    return 0;
}

后记与比赛体验

本次比赛感觉题目都不算很难,没有用到比较高级的算法和数据结构,还是比较适合普通人参加的。

比赛中比较想吐槽的是无法使用自己本地的编辑器(比赛网页检测到自己不在前台20次就会判定作弊),而网页编辑器的代码提示功能做得很差。建议要么换个好点的网络编辑器,要么就稍微放开一点限制(实际上由于是网络比赛,这种前台检测的防作弊方法也就防君子不防小人,选手完全可以用手机或者其它电脑查资料啊……)。

也正是由于上面这点,更多还是建议新手以做练习的心态来提升自己,没必要争排名,比赛含金量并不高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值