整数m-划分的词典序生成
在“词典序法生成整数划分”一文中,给出了整数划分的词典序生成算法的实现,那里讨论的是无限制的划分。在实际中,经常需要产生划分部分固定的算法,我们不妨称“n的m部分划分”为:“n的m-划分”。例如,产生11的4-划分,即产生:
8 1 1 1,7 2 1 1,6 3 1 1 ,5 4 1 1,6 2 2 1,5 3 2 1,4 4 2 1,4 3 3 1,5 2 2 2,4 3 2 2,3 3 3 2
本文给出整数m-划分的词典序算法。这里采用的颠倒的词典序。算法的基本思想是:从高位向低位,找出第一个可增加的位置p,增加之,并使p前面的部分最大化。算法用 C++ 实现如下:
if( n<m ) return ;
if( m==1 )...{
cout<<n<<endl; return ;
}
int *List=new int[m+1], t, s, j;
List[0]=n-m+1; List[m]=-1;
for( j=1; j<m; ++j ) List[j]=1;
while( 1 )...{
copy( List, List+m, ostream_iterator<int>(cout," ") ),cout<<endl;
if( List[1]<List[0]-1 )
--List[0], ++List[1];
else...{
j=2; s=List[0]+List[1]-1;
while( List[j]>=List[0]-1 )
s+=List[j], ++j;
if( j==m ) return ;
++List[j]; t=List[j--];
while( j>0 )
List[j--]=t, s-=t;
List[0]=s;
}
}
delete []List;
}
来看一个整数m-划分算法的应用:
试产生满足a1+a2+a3+a4+a5+a6+a7=175,其中a1>a2>a3>a4>a5>a6>a7>0 的所有组合(a1,a2,a3,a4,a5,a6,a7)。
怎么求解?第一个想法是:利用整数m-划分的词典序算法,产生175的7-划分,再从结果中剔除那些元素有重复的解。这个想法可以解决问题,但是效率不高:175的7-划分有9,962,502个,而满足条件的只有4,767,088个。下面给出一个算法,它利用整数m-划分算法,直接产生所有满足条件的解。
显然,直接利用整数m-划分算法是不能解决问题的。为什么不能?因为m-划分中含有重复项。那么,如何避免重复项产生?显然,整数m-划分的每一个解就是一个多重组合,如果我们知道如何将多重组合转化为元素值单一的组合,问题就解决了。转化的方法不止一个,其中一个比较好理解的是:
设组合(a1,a2,a3,...,am) 其中a1>=a2>=a3>=...>=am>0,为n选m的多重组合,那么,它对应于一个n+m选m个的单一组合(b1,b2,b3,...,bm),其中b1>b2>b3>...>bm>0,映射规则为:
bi=ai+m-i。 (1)
上面的转化方法的正确性是显然的,可以很简单地证明。由上面的转化方法,问题可以转化为:
产生满足:
a1+a2+a3+a4+a5+a6+a7=154,其中a1>=a2>=a3>=a4>=a5>=a6>=a7>0 的所有组合(a1,a2,a3,a4,a5,a6,a7)。
为什么可以转化成这样?因为由映射规则(1)知道,映射前后的组合元素之和的差值是m*(m-1)/2,对于m=7来说,差值是21,175-21=154。因此,我们只需要将算法实现中的输出部分按映射规则重写如下:
cout << List[j] + m - j - 1 << " " ;
cout << endl;
并以n=154,m=7来调用m_Partition( int n, int m ),就可以得到问题的解。
除了上面的问题外, 许多其他的问题都可以用整数m-划分算法来求解。比如,穷举n阶幻方时, 如果事先知道所有和为幻和的所有n*n选n的组合,那就可以降低搜索量。显然,可以由整数m-划分算法的变形产生元素和为幻和的n*n选n的所有组合:我们只需要在解决上面的问题的算法的基础上,改动初始化操作就可以完成这个任务,而且算法的效率也足够高。如果你用其他的算法来做这件事的话,效率就可能就没这么好了,比如,如果你采用:先用组合生成算法先产生n*n选n的所有组合,再剔除元素和不满足条件的组合 这样的方法,那么,即使你采用速度极快的组合生成算法,效率也是不敢恭维的:对n=7来说,它需要7-8秒,而采用整数m-划分算法的程序,只要大约50毫秒就完成了任务。