HDU 2062 Subset sequence


  • 原题链接:Here!

  •   一开始想直接用递归做,因为以前做过采用递归求全排列的问题,但是这样会走进一个误区,这个题目要求按字典序排,所以不能按照全排列的思想去解决。
    字典序这个要求比较有意思,从字典序中可以找到一些规律,用这些规律来解决掉这个问题。

  • 假设拿 n=3 m=10来说
    它的子集按照字典序排列会有如下顺序:

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

    从子集顺序上可以发现它们能够分成3组,每一组都有一个开头数字,去掉开头数字就是n=2时的情况数+1(空集)

    所以
    1.设f(n)是n个数字按照字典序所产生的子集个数f(n) = n*( f(n-1) + 1 ),f(1)=1 
    这里需要强调按照字典序生成的子集,一个含有n个元素的集合真子集的个数是2^n-1,为什么按照字典序生成的子集却不符合这一规律?因为在按字典序生成时{1,2}和{2,1}认为是两个不同的集合,所以 f(n) >> 2^n-1。

    2.设g(n)是每一组子集的个数,g(n)=f(n)/n 
    g(n-1)=f(n-1)/(n-1),f(n) = n*( f(n-1) + 1 ),g(n)=(n-1)*g(n-1)+1 


    从上面子集顺序可以得到一个思路,我们可以先输出开头数字,然后把问题规模缩小到( n-1 , m-(t-1)*g(n)-1 ),不断缩小规模直至找到答案
    怎么得到的 m-(t-1)*g(n)-1 ? t代表所求子集所在的组,每次问题规模缩小时,m都需要减去多余的子集,多余的子集数就是上面1~t-1组所含子集数和t组去掉开头数字后剩余的空集。


    主要步骤:
    1、求出所在组t
    2、输出所在组t的首元素s[t](同一组首元素相同)
    3、将该子集的下一个元素到最后一个的值+1,注意这个规律:在第i组,首元素为i,删除首元素后,在第i个子集后首元素均变大+1.
    4、缩减问题规模继续执行步骤1~3

  • CODE:

    #include<cstdio>
    using namespace std;
    
    #define LL long long 							// 因为上面分析f(n)<<2^n-1 因此g(n)和m用longlong
    //#define test
    
    int n;									// n:一共多少元素<=20。t:所求子集位于分组后的第几组  
    LL m;									// m:位于某一组的第几个子集  
    int s[21];								// 后面将子集按字典序分组后每组的初始首元素,组数<=20  
    
    LL g[21]={0};								// 后面将子集分组后平均每组个数,如:c[2]表示n=2时的分组每组中子集数  
    void set_table(){							// 先打个表 
    	for (i=1;i<21;i++)  
            g[i]=g[i-1]*(i-1)+1;						// 推导出来的c[n]=(n-1)*c[n-1]+1  
    } 
    int main(){  
    	#ifdef	test
    		freopen("Hdu 2062 Subset sequence.txt","r",stdin);
    	#endif
        set_table();
        while (scanf("%d%lld",&n,&m)!=EOF){  
            for(i=0;i<21;i++)  s[i]=i;					// 每循环一次就重新归位每组首元素  				
            
    		while (n>0&&m>0){  
                int t=m/g[n]+(m%g[n]>0?1:0);  				// 得到第m个子集在分组后的第t组
                if(t>0){									    
                    printf("%d",s[t]);  
                    for(i=t;i<=n;i++)  
                        s[i]=s[i+1];					// 当去掉开头数字后,大于开头数字的数+1 
                    m-=((t-1)*g[n]+1);					// 减去(t-1组总子集数+1),m变为表示在剩余子集中位于第几个  
                    putchar(m==0?'\n':' ');   
                }  
                n--;							// 依次递减,直到执行上面的if代码或退出  
            }  
        }  
        return 0;  
    }










  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值