本题来源于SJTUOJ P1012
Description
有一个数列,它是由自然数组成的,并且严格单调上升。最小的数不小于S,最大的不超过T。现在知道这个数列有一个性质:后一个数相对于前一个数的增长率总是百分比下的整数(如5相对于4的增长率是25%,25为整数;而9对7就不行了)。现在问:这个数列最长可以有多长?满足最长要求的数列有多少个?
Input Format
输入仅有一行,包含S和T两个数。
对于30%的数据,0<S<T≤1000<S<T≤100
对于100%的数据,0<S<T≤2000000<S<T≤200000Output Format
输出有2行。第一行包含一个数表示长度,第二行包含一个数表示个数。
Sample Input
2 10Sample Output
5
2样例解释
2 4 5 6 9以及2 4 5 8 10
先来看一下暴力做法。对[S,T)[S,T)中的每个数,枚举百分整数来得到所有可能的后继。不断重复这个过程,直到后继大于TT,说明我们找到了一个序列,将其长度和当前最大长度比较。如果大于最大长度,则更新之;如果等于,就把序列个数的计数器+1。这本质上是一个深度优先搜索,时间复杂度介于和O(100∗(T−S)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
结尾的最大长度的序列数目。现在考虑如何进行状态转移。
首先,枚举百分整数这一步是必不可少的。假设我们通过枚举找到了一个可能的后继next
,len[next]
表示以next
结尾的序列的最大长度,tms[next]
表示以next
结尾的最大长度的序列数目。由于next
是i
的后继,自然要把这些东西和len[i]
tms[i]
进行比较。考虑一下现有的以i
结尾的序列再加上next
,其长度为len[i] + 1
,把它和len[next]
比较。这时,有三种情况:
len[i] + 1 < len[next]
:还没有原来已经找到的序列长,不做任何操作。len[i] + 1 == len[next]
:一样长。由于当前的len[next]
里保存的是最大长度,我们需要更新最大长度len[next]
的次数,即tms[next] += tms[i]
(想想为什么不是++tms[next]
)。len[i] + 1 > len[next]
:出现了更长的序列。那么我们要进行两个更新:长度的len[next] = len[i] + 1
和次数的tms[next] = tms[i]
。
每次循环,都要更新当前的总的最大长度和相应的次数。为了方便记录这个次数,我们再加一个数组cnt
,其中cnt[i]
表示长度为i
的序列的数目。
至此思路已经完成,时间复杂度是O(100∗(T−S+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的合理性给出证明。即证明:∀x∈N+∀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%可以得到最长序列,而其正确性尚有待证明。