【51nod 3046】子集和的元素和(DFS)(二分)

题目大意

给出长度为 n n n 的集合 A A A A A A 的非空子集合共有 2 n − 1 2^n-1 2n1 个,每个子集合有一个元素的加和 S u m Sum Sum 。求所有 S u m Sum Sum 中第 K K K 小的 S u m Sum Sum

输入格式

第一行: 2 2 2 个数 n , k n,k n,k 。( 1 ≤ n ≤ 50000 , 1 ≤ k ≤ 200000 1\le n\le 50000, 1\le k\le 200000 1n50000,1k200000 ) 第 2 ∼ n + 1 2 \sim n+1 2n+1 行:每行一个数 A i Ai Ai 。( 1 ≤ A i ≤ 1 0 9 1\le Ai\le 10^9 1Ai109

输出格式

输出一个数,对应第 K K K 小的 S u m Sum Sum

输入样例

3 5
1
2
3

输出样例

4

数据范围

对于 16 16% 16 的数据, 1 ≤ n ≤ 20 , 1 ≤ k ≤ 100 1\le n\le 20, 1\le k\le 100 1n20,1k100 1 ≤ A i ≤ 50 1\le Ai\le 50 1Ai50

对于 60 60% 60 的数据, 1 ≤ n ≤ 2000 , 1 ≤ k ≤ 10000 1\le n\le 2000, 1\le k\le 10000 1n2000,1k10000 1 ≤ A i ≤ 2 × 1 0 6 1\le Ai\le 2\times 10^6 1Ai2×106

对于 100 100% 100 的数据, 1 ≤ n ≤ 50000 , 1 ≤ k ≤ 200000 1\le n\le 50000, 1\le k\le 200000 1n50000,1k200000 1 ≤ A i ≤ 1 0 9 1\le Ai\le 10^9 1Ai109

样例解释

1 , 2 , 3 1,2,3 1,2,3 的非空子集合包括: ( 1 ) , ( 2 ) , ( 3 ) , ( 1 , 2 ) , ( 1 , 3 ) , ( 2 , 3 ) , ( 1 , 2 , 3 ) (1),(2),(3),(1,2),(1,3),(2,3),(1,2,3) (1),(2),(3),(1,2),(1,3),(2,3),(1,2,3) 。对应元素和为: 1 1 1 2 2 2 3 3 3 3 3 3 4 4 4 5 5 5 6 6 6 。其中第 5 5 5 小的为 4 4 4

基本思路

我们看到 n n n 的范围有 50000 50000 50000 ,所以 A A A 的非空集合数量是 2 50000 − 1 2^{50000} - 1 2500001 ,这是绝对不可能完全枚举的。

这种情况下如何求第 k k k 大的呢?这需要我们转换思路,从求第 k k k 大转换为猜第 k k k 大的是多少?

k k k 越小对应的结果越大, k k k 越大对应的结果越小,因此 k k k 同结果直接具有单调性,我们用二分来猜测最终的结果,用 D F S DFS DFS 来验证结果的数量

即二分最终的答案,然后利用 D F S DFS DFS 统计有多少组解小于这个答案,但需要注意的是, D F S DFS DFS 时当解的数量达到了 K K K 个就要退出(边界条件),否则复杂度无法保证

这样时间复杂度为 O ( n + k ( l o g ( s u m ) ) ) O(n+k(log(sum))) O(n+k(log(sum))) ,其中 s u m sum sum 为所有数字的和。

但实际提交时我们会发现它还是会超时,我尝试对原数组排序来优化它的速度,最后发现对原数组降序排序之后,程序运行堪称神速,这个我没有仔细去验证,但这个是个必要的优化。

核心代码

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=5e4+10;
ull n,k,a[N],cnt,l,r,mid;
inline void dfs(ull p,ull sum,const ull num){
	if(sum>=num) return;
	if(cnt>k) return;//这一步至关重要
	cnt++;
	for(register ull i=p;i<=n;i++){
		if(sum+a[i]<num) dfs(i+1,sum+a[i],num);
		if(cnt>k) return;
	}
}
inline bool check(ull x){
	cnt=0;
	dfs(1,0,x);
	if(cnt>k) return false;
	return true;
}
bool cmp(ull x,ull y){
	return x>y;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>k;
	for(register int i=1;i<=n;i++){
		cin>>a[i];
		r+=a[i];
	}
	sort(a+1,a+1+n,cmp);
	l=a[n];
	while(l<r){
		mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l;
	return 0;
}
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值