对这道题地思路比较陌生,看解答之后消化了很久才弄懂,所以写个详细的笔记。
题目大意是输入3个整数 N,L,I,N表示2进制串的长度,L表示这个串中最多有多少个1,I表示要求的串的序列(把所有符合要求的串从小到大排序,取第I个)。
最朴素的办法就是从0开始枚举,把所有遇到数t,求它的位数,若满足要求就把I-1,当I==0时退出,此时记录的t为所求。
但是数据范围最大涉及到32位的枚举,这么做会超时。(不过用朴素的解法来理解题意还是很方便的)。
既然不能一一枚举,那我们就要用集合的观点来解决,若能确定属于哪个集合,用最笨的办法在集合中枚举,效率也会高很多(为神马我总是想枚举啊....),当然有更好更方便的办法。
划分集合是最关键的步骤,1的个数多的不一定就比1个数少得序列排后(如1000和11),所以我们要用2维来描述这个集合。
定义f[i,j]为长度为i,最多不超过j个1(这样定义j可以不考虑j>i的情况),的串的个数,可以根据f[i,j]来划分集合。
根据定义 有
f[i][j] = f[i-1][j-1] + f[i-1][j] (到这里可以发现有dp的味道了)
其中f[i-1][j-1]的实际意义为 在f[i-1][j-1]所确定的集合中的串前面(‘数’都用‘串’代替,好理解)添加1;
类似的f[i-1][j]表示在前面添加0。
同时由定义可以知道 对任意 i1,j1<i2,j2 集合f[i1][j1]是f[i2][j2]的真子集。
对于初始的f[N][L],如果有f[N][L-1]>=I (保证有解,所以必有f[N][L]>=I) I肯定是属于f[N][L-1]集合的,
继续这样分析,直到f[N][k]>=I f[N][k-1]<I , 那么可以知道这个序号为I的串应该有k个1,因为它属于f[N][k]但不属于f[N][k-1] 这2个集合的交集就是表示仅有k个1的集合。
接下来我们要确定这些1的位置。可以用同样地办法,若I串的首位1出现在第N位,F[N][l]>=I,F[N-1][l]<I。
也就是说第N位为1的串可以用 F[N][l] - F[N-1][l]来表示,换句话说,确定该位的1后,剩下的串应该在集合f[i-1][l-1]中。如果F[N-1][l]也>I,就向更小的集合F[N-1][l]查找,所求串应该是这个集合中的第I位,同时N位补0。
确定l和确定各位上的数可以合并为1个步骤。因为L本身的定义就让f[N][L]包含了f[N][l],不必知道l的具体值,只要依次确定每一位上的数即可,事实上当这一过程完成后,l的值当然也确定了。
到现在为止,该题的算法已经讨论清楚,每一位都可以这样递归求解。
不过因为几个细节问题我还是wa了几次。
首先递归边界应该是i=0时跳出,即已经把N~1位的情况都确定了,整个过程就可以结束,而不是当I=0时跳出,因为I在是表示剩下的串在当前集合中的序列,不是表示它前面还有多少个序列.
第二是f[i][j]的初始化, 由于其定义,不必考虑j>i的问题 ,所以
for(i=0;i<=32;i++)f[0][i]=1;
for(i=1;i<=32;i++)
for(j=0;j<=32;j++)
{ if(j==0)f[i][j]=1; else f[i][j]=f[i-1][j]+f[i-1][j-1];
这样就足够了,我写得时候过多的在意j>i的问题,写得很复杂,还出错.
拓展:
把f[i][j]所表示的集合再细分,会发现更多的东西。
f[i][j]定义:为长度为i,最多不超过j个1的串的个数。
在定义
c[i][j]: 长度为i,拥有j个1的串的个数。
f[i][j]=c[i][0]+...+c[i][j]。
其实c[i][j]就是组合数(在i个位置放j个1的方法数,此时应该注意j>i的问题,组合数不允许这样)。
通过 f[i][j]=f[i-1][j-1] + f[i-1][j] 可以得出 c[i-1][j]+c[i-1][j-1]=c[i][j] 的公式,
所以这道题dp方程的推导有一个组合证明。
本文使用Blog_Backup未注册版本导出,请到soft.pt42.com注册。