hdu2062(dp解决找子集问题)

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

比如一个an{1,2,…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个时,其对应的排列顺序总数为:f(n) = 2*(f(n-1)+1)
如n=3时,其就相当于是分成了3组,分别为1组,2组,3组。然后每一个组的构成,如1组由空集数(也就是一个)+(小2组+小3组)构成。所以一次类推就得到了上面的递归公式:f(n) = 2(f(n-1)+1)【其中的1就是空集数】
然后我们只需要每次输出前,先算出m数是哪个组的,再将这个组数所代表的这个数输出了即可,已经输出了的用数组记录一下,下一次输出算的时候就不算上它,就行了。
实例说明】比如:拿n=3,m=10来说,
首先呢,n=3,可以通过上面的递归式子算出来是总共为15种;
可以算出,分成了3组,每组的个数为:15/3=5个
看的出10/5为第2组(注意:如果是7,8之类的余数并不是0的话组数是要加1的
然后就将这个2输出,并将数组a中的a[2]=1
下次如果检测到就跳过
然后再将m置为这一组当中的第几位,如果是第一位的话(因为是空集就可以直接结束了)。如果是其他位的话,就存入并-1进入下次计算,所以当前的m被置为(5-1)。 而n的话也-1。
所以就变成了n=2,m=4
然后n=2中,总数为4,组数就分成了2组,每组个数为:4/2=2个,m是在第二组内且对应位置所以变为m=2。又因为前面a[2]=1了,所以遍历到2时将会跳过,就会输出3。m-1和n-1操作
所以变成了n=1,m=1
再进行输出1.
检测到m=1就结束。
所以n=3,m=10,程序会输出231

ac代码:

#include<stdio.h>

typedef long long ll;

ll value[25];

void init(){//初始化每个n对应的子集总个数
	
	value[1] = 1;//第一个只有一个子集 
	for(int i = 2;i <= 20;++i){
		value[i] = i*(value[i-1]+1);
	}
	
}

int solve(int a[],int n,ll m,int flag){//flag用来标识是否为第一次进入,
							//如果是的话输出就后面不加空格,否则就加空格 
	ll temp = value[n]/n;//一组有多少数量
	int group = 0;
	int j = 0;
	int i = 0;//用来输出目标数字
	if(m % temp == 0){
		group = m / temp;
		m = temp;
	}else{
		group = m / temp + 1;
		m = m % temp;
	}
	for(i = 0;j != group;i++){//查找数组中第group个非0下标 
		if(a[i+1] == 1) continue;//因为i初始值是0开始的,而我们要输出的初始值是1开始的,错位了一个1 
		j++;
	}
	if(flag != 1){
		printf("%d",i);
	}else{
		printf(" %d",i);
	}

	a[i] = 1;
	if(m == 1 || n-1 == 0){
		return 0;
	}
	flag = 1;
	
	return solve(a,n-1,m-1,flag);
	
	
		
}



int main(){
	int n;
	ll m;
	init();//初始化每个n对应的子集总个数
	while(scanf("%d%lld",&n,&m) != EOF){
		int a[25] = {0};
		solve(a,n,m,0);
		printf("\n");
		
	}

	return 0;
	
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值