题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2062
题目翻译:给出一个N代表有一个集合,集合有1~N中的数字组成。集合的所有子集,
非空集,按照字典序排序,给出一个M,求第M个集合的元素。
解题思路:数学找规律。
N = 1 其集合排序如下
{1}
-------------------------------------------------------------------------------
N = 2 其集合排序如下
{1}
{1,2}
{2}
{2,1}
-------------------------------------------------------------------------------
N = 3 其集合排序如下
{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个数的集合S,其子集个数可以这样计算。
从N个数中选择1个,然后进行全排列 C(n,1)*A(1,1)
从N个数中选择2个,然后进行全排列 C(n,2)*A(2,2)
。
。
。
从N个数中选择M个,然后进行全排列 C(n,m)*A(m,m) (M<=N)
则N个元素的集合共有sum = C(n,1)*A(1,1) + C(n,2)*A(2,2) + .......+ C(n,m)*A(m,m)
则为了确定第一个元素,我们可以用sum确定出第一个数字所在的组,然后可以进行取余操作
确定是某组的第几个,然后这个问题就变成了刚才问题的子问题,按照该方法继续解决就可以
正确求解。
题目易错点:输入的M会很大,所以要用long long ,刚开始没注意到M用的是int类型,提交一直
TLE。原因是当M溢出的时候,其值为负数,无法结束我程序中写的循环。
AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
int ans[50];
long long power[30];
long long num[30]; //num[i]用来记录N个数的时候有多少个子集
int vis[30];
int cnt;
void solve(int N,long long M) {
cnt = 0;
memset(vis,0,sizeof(vis));
int rest = N;
while(true) {
//cout<<"M = "<<M<<endl;
int temp = M/num[rest]; ///计算答案在第几组
if(M%num[rest] != 0)
temp++;
int ccount = 0;
for(int i = 1; i <= N; i++) {
if(vis[i]==0) {
ccount++;
if(ccount == temp) {
vis[i] = 1;
ans[cnt++] = i;
rest--; ///数字减少一个
break;
}
}
}
M = M-(temp-1)*num[rest+1]; ///计算它是该组的第几个
M--;
if(M == 0)
break;
}
}
int main() {
int N;
long long M,Mul = 1;
power[0] = 1;
for(int i = 1; i <= 20; i++) {
power[i] = Mul*i;
Mul = Mul*i;
}
for(int i = 1; i <= 20; i++) {
num[i] = 0;
for(int j = 1; j <= i; j++) {
num[i] = num[i] + power[i]/power[i-j];
}
num[i] = num[i]/i;
}
while(~scanf("%d%lld",&N,&M)) {
solve(N,M);
printf("%d",ans[0]);
for(int i = 1; i < cnt; i++) {
printf(" %d",ans[i]);
}
printf("\n");
}
return 0;
}