【SJTUOJ笔记】P1012 增长率问题

该博客详细介绍了SJTUOJ上的一道算法题,讨论了一个自然数序列,其增长率始终是百分比下的整数。博主首先描述了暴力解法的时间复杂度问题,然后提出了使用动态规划优化解决方案,通过状态转移矩阵来跟踪序列的最大长度和数量。博主还提到,虽然增长率通常限制在100%以内能确保序列的最长长度,但存在反例证明这不是必要的条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本题来源于SJTUOJ P1012

Description

有一个数列,它是由自然数组成的,并且严格单调上升。最小的数不小于S,最大的不超过T。现在知道这个数列有一个性质:后一个数相对于前一个数的增长率总是百分比下的整数(如5相对于4的增长率是25%,25为整数;而9对7就不行了)。现在问:这个数列最长可以有多长?满足最长要求的数列有多少个?

Input Format

输入仅有一行,包含S和T两个数。
对于30%的数据,0<S<T1000<S<T≤100
对于100%的数据,0<S<T2000000<S<T≤200000

Output Format

输出有2行。第一行包含一个数表示长度,第二行包含一个数表示个数。

Sample Input
2 10

Sample Output
5
2

样例解释
2 4 5 6 9以及2 4 5 8 10

先来看一下暴力做法。对[S,T)[S,T)中的每个数,枚举百分整数来得到所有可能的后继。不断重复这个过程,直到后继大于TT,说明我们找到了一个序列,将其长度和当前最大长度比较。如果大于最大长度,则更新之;如果等于,就把序列个数的计数器+1。这本质上是一个深度优先搜索,时间复杂度介于O(100(TS))O(100(TS)2)O(100∗(T−S)2)之间,实测通过了90%的数据(很显然数据太水了),最后一个测试点超时。实现如下:

void dfs(int x, int CurLen){ //采用递归来进行搜索
    for (int p = 1; p <= 100; ++p){ //枚举百分整数
        if (p * x % 100 == 0){ //找到了一个后继
            int next = x + p * x / 100; //计算后继的值
            if (next > t) return; //超出范围,舍弃
            if (CurLen + 1 == MaxLen) ++cnt; //加上后继的长度等于最大长度,更新计数器
            else if (CurLen + 1 > MaxLen){ //大于最大长度,同时更新最大长度和计数器
                MaxLen = CurLen + 1;
                cnt = 1;
            }
            dfs(next, CurLen + 1); //以next为结尾,搜索下一个数
        }
    }
}
//中间略去
for (int i = s; i <= t; ++i)
    dfs(i, 1); //枚举起点进行搜索
//最后结果是MaxLen和cnt

再想一想,这道题肯定是要动态规划的。那么以什么作为状态呢?根据经验,我们用len[i]来表示以i结尾的序列的最大长度。因为题目还要求最大长度的个数,我们再用tms[i]表示以i结尾的最大长度的序列数目。现在考虑如何进行状态转移。
首先,枚举百分整数这一步是必不可少的。假设我们通过枚举找到了一个可能的后继nextlen[next]表示以next结尾的序列的最大长度,tms[next]表示以next结尾的最大长度的序列数目。由于nexti的后继,自然要把这些东西和len[i] tms[i]进行比较。考虑一下现有的以i结尾的序列再加上next,其长度为len[i] + 1,把它和len[next]比较。这时,有三种情况:

  1. len[i] + 1 < len[next]:还没有原来已经找到的序列长,不做任何操作。
  2. len[i] + 1 == len[next]:一样长。由于当前的len[next]里保存的是最大长度,我们需要更新最大长度len[next]的次数,即tms[next] += tms[i](想想为什么不是++tms[next])。
  3. len[i] + 1 > len[next]:出现了更长的序列。那么我们要进行两个更新:长度的len[next] = len[i] + 1和次数的tms[next] = tms[i]

每次循环,都要更新当前的总的最大长度和相应的次数。为了方便记录这个次数,我们再加一个数组cnt,其中cnt[i]表示长度为i的序列的数目。
至此思路已经完成,时间复杂度是O(100(TS+1))O(100∗(T−S+1)),空间复杂度O(T)O(T),算是一个不错的结果。实现如下:

for (int i = s; i <= t; ++i){ //初始化
    len[i] = 1; //以i结尾的序列至少有一个{i},所以长度初始化为1
    tms[i] = 1; //与上同理
}
    cnt[1] = t - s + 1; //目前只知道存在长度为1的序列,且有t-s+1个
    long long MaxLen = 1; //最长长度初始化为1
    for (int i = s; i < t; ++i){ //枚举起点
        for (int j = 1; j <= 100; ++j){ //枚举百分整数
            if (i * j % 100 == 0){ //找到一个后继
                int next = i + i * j / 100; //计算后继的值
                if (next > t) continue; //超出范围,舍弃
                if (len[next] == len[i] + 1){ //情况2:一样长
                    tms[next] += tms[i];
                }
                else if (len[next] < len[i] + 1){ //情况3:更长
                    len[next] = len[i] + 1;
                    tms[next] = tms[i];
                }
                MaxLen = (MaxLen > len[next] ? MaxLen : len[next]); //更新最长长度
                cnt[len[i] + 1] += tms[i]; //找到一个next,无论以上比较结果怎样,我们都找到了tms[i]个长度为len[i]+1的新序列
            }
        }
    }
//最后结果是MaxLen和cnt[MaxLen]

最后,仍然存在一个问题:理论上我们需要对枚举百分整数只从1到100的合理性给出证明。即证明:xN+∀x∈N+,若存在可行的序列{x,x×p%}(p>100){x,x×p%}(p>100),则必然存在更长的可行序列{x,x×p1,...,x×pn%,x×p%}{x,x×p1,...,x×pn%,x×p%},使得每一步的增长率都不大于100%100%。然而,这个命题是不成立的。简单举出一个反例:100->211。那么只能说,每次都让增长率不大于100%可以得到最长序列,而其正确性尚有待证明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值