杭电OJ2062(C语言)

题目

Problem Description

Consider the aggregate An= { 1, 2, …, n }. For example, A1={1}, A3={1,2,3}. A subset sequence is defined as a array of a non-empty subset. Sort all the subset sequece of An in lexicography order. Your task is to find the m-th one.

Input

The input contains several test cases. Each test case consists of two numbers n and m ( 0< n<= 20, 0< m<= the total number of the subset sequence of An ).

Output

For each test case, you should output the m-th subset sequence of An in one line.

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

思路

对于An的个数,显然是存在规律的。

  • n = 1 时,只有{1}一个子集
  • n = 2 时,有如下四个子集
    {1} {1,2}
    {2} {2,1}
  • n = 3时,有如下15个子集。
    {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}
  • n = k时,…
    进一步对其剖析,将子集分组。n=2可以分为2组,n=3可以分为3组,…,n = k就可以分k组。这个分组显然是根据第一个元素来区分的。找到这个规律就好办,当n = k时,我把第一个元素确定好后,剩下k-1个元素的确定不就是n = k-1时的情景。因此我们就得出求子集个数的递推关系式如下
    n u m ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] 其 中 n u m ( 1 ) = 1 num(k) = k*[num(k-1)+1] \\其中num(1) = 1 num(k)=k[num(k1)+1]num(1)=1
    公式中num(k)代表n=k是,包含子序列个数,里面加了一个1是因为存在一种情况,即{k}单独存在,不包含任何k-1的子序列这种情况,因此加了个1。
    上面的描述,其实就有点动态规划的影子了,但我们这题的关键不在于求解子集个数,而是要根据这些关系求出第m个排序的具体序列。显然根据之前的分组来说,我们可以先得出m在哪一组,确定了在哪一组后,其输出序列第一位P1就确定了。
    根据关系我们不仅可以求出它在第几组,还可以求出它在第几组第几个。显然我们可以据此采用重复的上述步骤求解下一个P2,此后依次求解其他…
    下面以一个例子说明。

例子

求n=3,m=10时的输出序列
可以看出一共存在3个组,每个组的个数num_perGroup现在成为我们需要掌握的。显然 n u m _ p e r G r o u p ( k ) = n u m ( k ) / k num\_perGroup(k) = num(k)/k num_perGroup(k)=num(k)/k,下面我们推到k和k-1每组个数的关系:

n u m ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] num(k) = k*[num(k-1)+1] num(k)=k[num(k1)+1]
n u m _ p e r G r o u p ( k ) = k ∗ [ n u m ( k − 1 ) + 1 ] / k = n u m ( k − 1 ) + 1 num\_perGroup(k) = k*[num(k-1)+1]/k =num(k-1)+1 num_perGroup(k)=k[num(k1)+1]/k=num(k1)+1
n u m ( k − 1 ) = n u m _ p e r G r o u p ( k − 1 ) ∗ ( k − 1 ) num(k-1) =num\_perGroup(k-1)*(k-1) num(k1)=num_perGroup(k1)(k1)
n u m _ p e r G r o u p ( k ) = n u m _ p e r G r o u p ( k − 1 ) ∗ ( k − 1 ) + 1 num\_perGroup(k) = num\_perGroup(k-1)*(k-1)+1 num_perGroup(k)=num_perGroup(k1)(k1)+1

最后一个公式即是相邻两者每个组包含子集个数的关系。显然num_perGroup(1) = 1,num_perGroup(2) = num_perGroup(2-1)×1+1 = 2,同理num_perGroup(3) = 2×2+1 = 5。

  • round1:对于m=9,显然它在第2组,第4个数,因次可以确定P1=2。
  • round2:输出第一个数2后,还剩1,3两个数,显然现在情况更新为n=2,m=4-1=3这种情况(这里-1是为了排除只输出一个2就没有了这种情况,若减了后为0,那就输出序列仅为2,否则继续输出)。显然它在第二组,第一个数。而剩下的两个数中,第二组即是开头为3的情况,因此可以确定P2=3。
  • round3:输出第二个数3后,只剩1这个数,他的情况继续更新为n=1,m=1-1=0(依旧减了一个1).显然它成了我们之前说的输出为空的情况,这种情况即不输出。
  • 综上序列只有两位即2 3。

代码

#include <stdio.h>

int main() {
    long long num_perGroup[21],m;//num_perGroup:下标i中每组的个数,比如num_perGroup[3]=5
    int n,i,group,print_num[21];//print_num是根据计算出的位置打印数
    num_perGroup[1] = 1;
    for (i = 2; i <= 20 ; ++i)//根据前文推理的公式,初始化每组个数
        num_perGroup[i] = (i-1) * num_perGroup[i - 1] + 1;
    while (scanf("%d%lld",&n,&m)!=EOF){
        for (i = 1; i <=20 ; ++i)//初始化打印数,打印第i个位置就是i,随着输出进行,其会发生变化
            print_num[i] = i;
        while (m){//当m更新为0时就不需要再打印即退出
            group = (m-1)/num_perGroup[n] + 1;//计算当前打印数,即计算为第几组,
            printf("%d",print_num[group]);//根据组号打印
            for (i = group; i < 20 ; ++i)//打印后,把打印完的覆盖,之后不会在打印
                print_num[i] = print_num[i+1];
            m = m % num_perGroup[n];//计算在组内的位置
            if(m==0)//在最后一个位置
                m = num_perGroup[n];
            m--;//去除第一个不要任何数跟着的情况比如{3}{2}{1},这在后续迭代中很重要,否则会计算组数错误。
            n--;//打印出了一个,因此n要减去一个
            if(m)//判断是否打印完
                printf(" ");
            else
                printf("\n");
        }
    }
}

参考

思路来源:https://blog.csdn.net/a939682389/article/details/79273354

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
引用\[2\]提供了关于如何在杭电OJ上使用C语言进行题目刷题的步骤。首先,你需要打开百度并搜索“杭电OJ”,然后进入官网进行注册。在注册界面上填写完整的信息后点击提交。接下来,你可以使用你的用户名和密码登录账号。进入刷题界面后,你可以选择C语言作为答题语言,并将代码粘贴到答题界面中。然后你可以查看答案是否正确。如果你不小心离开了界面,你可以通过点击红线圈出部分或者点击右上方的用户名进入用户界面来查看你的题目是否正确。\[2\] 引用\[1\]提供了一段C语言代码,但是没有明确指出这段代码的作用和题目的具体要求。如果你能提供更多关于杭电OJ1098题目的信息,我将能够为你提供更准确的帮助。 #### 引用[.reference_title] - *1* [杭电OJ 2007 C语言版 已通过](https://blog.csdn.net/m0_56912916/article/details/119041984)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [杭电OJ刷题指南(ACM)](https://blog.csdn.net/qq_38769551/article/details/101510000)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值