题解报告:hdu 2062 Subset sequence

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2062

Problem Description
考虑集合An = {1,2,...,n}。 例如,A1 = {1},A3 = {1,2,3}。 子集序列被定义为非空子集的数组。 按照字典顺序排列An的所有子集。 你的任务是找到第m个。
Input
输入包含多个测试用例。 每个测试用例由两个数字n和m组成(0 <n <= 20,0 <m <= An的子集序列总数)。
Output
对于每个测试用例,您应该在一行中输出An的第m个子集序列。
Sample Input
1 1
2 1
2 2
2 3
2 4
3 10
Sample Output
1
1
1 2
2
2 1
2 3 1
解题思路:题目的意思就是求第m个按字典序排列的子集(非空)。这里我们先找一下规律:假设f(n)为An的子集的总个数,a(n)为An中每组(首元素相同的一组)的子集合个数,则有:

当n=1时,f(1)=1,a(1)=1,有1个子集合:{1};     

当n=2时,f(2)=4=2*(f(1)+1),a(2)=2=f(2)/2=f(1)+1,有4个子集合:{1},{1, 2};

                                    {2},{2, 1}。

当n=3时,f(3)=15=3*(f(2)+1),a(3)=5=f(3)/3=f(2)+1,有15个子集合: {1}, {1, 2}, {1, 3},{1, 2, 3}, {1, 3, 2};

                                        {2}, {2, 1}, {2, 3}, {2, 1, 3}, {2, 3, 1}

                                      {3}, {3, 1}, {3, 2}, {3, 1, 2}, {3, 2, 1}。

由以上规律得知:f(n)=n*(f(n-1)+1),a(n)=f(n)/n=f(n-1)+1,而a(n-1)=f(n-1)/(n-1),所以f(n-1)=(n-1)*a(n-1),

即有a(n)=a(n-1)*(n-1)+1;这就推导出每组(首元素相同的一组)的子集合个数。

下面举个栗子:n=3m=10,m表示求n的第m个子集合,首先我们已经按照推导公式将a数组(求每组的子集合个数)打表,接下来求m是在第几个大组,t=m/a[n]+(m%a[n]>0?1:0)=10/5+0=2,表示此时该集合在第二大组,此时直接输出首元素2,剩余1,3这两个元素,该集合{2,3,1}的位置为m-(t-1)*a[n]=10-(2-1)*5=5,表示在第二大组的第5个集合这个位置,删除集合中有'2'这个元素后,剩余集合按字典序排得到{1},{1,3};                                                                                        {3},{3,1}。

此时有两个大组,我们知道,剩下的要求的元素所在集合为第4个位置,n为原来的3个大组减去首元素都为2的这一组后变为2个大组即n=2,执行t那条语句得t=4/2+0=2,则所求当前集合在第2大组的第m-(t-1)*a[n]=4-(2-1)*2=2个位置,即所求集合在所有集合中的位置为m=10-(5+1)=4,所以此时输出首元素3,剩下1这个元素,删掉集合中有'3'这个元素后,剩下集合为{1},易知当前位置为1,即所求集合在所有集合中的位置m=4-(2+1)=1n=1,执行t语句后t=1/1+0=1,验证m-(t-1)*a[n]=1-0=1,对应得到接着去掉'3'元素后剩下子集合中的第1大组第1个位置,所以此时输出首元素1,删掉集合中有1这个元素后变成空集,即m=1-(0+1)=0n=0退出循环查找,即得到n=3时的第10个子集合。

结合以上规律,可以得到循环中m=m-(a[n]*(t-1)+1)直到m==0退出,并且每输出一个元素,相应的n大组数减1,直到n==0退出。

AC代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int n,s[21],t;//n表示一共有多少个元素,s是后面将子集按照字典序分组后每组的初始首元素,组数<=20,t是所求子集位于分组后的第几位
 6     long long m,a[21]={0};//m是位于第几个子集,后面是将子集分组后平均每组的个数,如c[2]表示n=2时的分组每组中子集数
 7     for(int i=1;i<21;i++)
 8         a[i]=a[i-1]*(i-1)+1;//推导出来的,表示An中每一组的子集合个数
 9     while(cin>>n>>m){
10         for(int i=0;i<21;i++)
11             s[i]=i;//每循环一次就重新归位每组首元素
12         while(n>0 && m>0){//
13             t=m/a[n]+(m%a[n]>0?1:0);//先记录在第几个大组
14             if(t>0){//得到第m个子集在分组后的第t组,若t>0
15                 cout<<s[t];//先输出该组的首元素
16                 for(int i=t;i<=n;i++)
17                     s[i]=s[i+1];//或s[i]+=1,我们发现:第n组中,第2个元素在第n个时变为它的下一个数
18                 m-=((t-1)*a[n]+1);//m-(t-1组总子集数+1),m表示在剩余子集中位于第几个
19                 putchar(m==0?'\n':' ');//m最后为0,直接输出换行,不然就输出空格
20             }
21             n--;//减掉首元素的一整组
22         }
23     }
24     return 0;
25 }

猜你喜欢

转载自www.cnblogs.com/acgoto/p/8906186.html