HDU 2062 Subset sequence

我是把它当做一道数学题来做的。

这篇题解写的有点啰嗦,但是是我最原始的思维过程。

对于一个集合An= { 1, 2, …, n },在n比较小的情况下,在纸上按字典顺序把所有子集排列一下。

以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
n=3的情况

容易看出前5个打头的是1,紧接着5个子集打头的是2,最后5个开头的是3。

拿前五个来说,除了第一个,后面四个不看开头的1,后面的排列形式和n=2的子集的排列很相似。

f(n)代表集合An所有子集的个数,那么有递推关系:

f(n) = n * (f(n - 1) + 1), f(1) = 1

 

这里数组taken的作用就是标记某个数是否被占用。

在这个例子里面,要求第一个数,计算(10 - 1) / 5 + 1 = 2。

表示这个数是所有未被占用的数里面从小到大第2个数,也就是2。

再计算一下余数r = (10 - 1) % 5等于4

如果r == 0说明后面的数没有了,跳出循环。

否则m = r;

继续下一轮循环

这里m == 4,计算第二个数 (4 - 1) / 2 + 1 == 2。

现在2已经被第一个数占用了,所以未被占用的第二个数就是3。

后面依次类推。

 

 1 //#define LOCAL

 2 #include <iostream>

 3 #include <cstdio>

 4 #include <cstring>

 5 using namespace std;

 6 

 7 int main(void)

 8 {

 9     #ifdef LOCAL

10         freopen("2062in.txt", "r", stdin);

11     #endif

12 

13     int n;

14     bool taken[25];

15     int b[25];

16     long long m, a[25];

17     a[1] = 1;

18     for(int i = 2; i <= 20; ++i)

19         a[i] = i * (a[i - 1] + 1);

20 

21     while(scanf("%d%I64d", &n, &m) == 2)

22     {

23         memset(taken, false, sizeof(taken));

24         int i;

25         long long r = 1;

26         for(i = 1; i <= n; ++i)

27         {

28             b[i] = ((m - 1) / (a[n - i] + 1)) + 1;

29             int j, k = 0;

30             for(j = 1; j <= n; ++j)

31             {

32                 if(!taken[j])

33                     ++k;

34                 if(k == b[i])

35                     break;

36             }

37             b[i] = j;

38             taken[j] = true;

39             r = (m - 1) % (a[n - i] + 1);

40             if(r == 0)

41                 break;

42             m = r;

43         }

44         for(int j = 1; j < i; ++j)

45             printf("%d ", b[j]);

46         printf("%d\n", b[i]);

47     }

48     return 0;

49 }
代码君

 

你可能感兴趣的:(sequence)