【SJTUOJ笔记】P1012 增长率问题

本题来源于SJTUOJ P1012

Description

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

Input Format

输入仅有一行,包含S和T两个数。
对于30%的数据, 0<S<T100 0 < S < T ≤ 100
对于100%的数据, 0<S<T200000 0 < 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 ) 中的每个数,枚举百分整数来得到所有可能的后继。不断重复这个过程,直到后继大于 T T ,说明我们找到了一个序列,将其长度和当前最大长度比较。如果大于最大长度,则更新之;如果等于,就把序列个数的计数器+1。这本质上是一个深度优先搜索,时间复杂度介于 O(100(TS)) O ( 100 ∗ ( T − S ) ) 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 × p 1 , . . . , x × p n % , x × p % } ,使得每一步的增长率都不大于 100% 100 % 。然而,这个命题是不成立的。简单举出一个反例:100->211。那么只能说,每次都让增长率不大于100%可以得到最长序列,而其正确性尚有待证明。

你可能感兴趣的:(算法与数据结构)