BJ模拟 第k小和(DFS+二分+折半)

Description

【问题描述】

    从n个数中选若干(至少1)个数求和,求所有方案中第k小的和(和相同但取法不同的视为不同方案)。
【输入格式】
    第一行输入2个正整数n,k。
    第二行输入这n个正整数。
【输出格式】
    输出第k小的和。
【样例输入】
5 12
1 2 3 5 8
【样例输出】
8
【样例解释】
    前12小的和分别为:1 2 3 3 4 5 5 6 6 7 8 8
【数据规模和约定】
    测试点1,n<=16。
    测试点2-3,n<=100,k<=500。
    测试点4-5,n<=1000,k<=5000。
    测试点6-8,n<=10^5,k<=5*10^5。
    测试点9-10,n<=35。
    对于所有数据,1<=k<2^n,n个正整数每个都不超过10^9。

 

对于测试点1-8可采用DFS方式:

对于原序列中数排序,之后每个数可采取两种选择 将1记为选 0记为不选

那么可构造出一棵搜索树


但直接搜索状态数过多 且有许多无用状态(0->0、0->0、0、0)

所以可以优化

用二元组(sum,i)表示和为sum,当前考虑第i个位置是否要选。
先将二元组(a[1],2)放入队列 之后每次取队首元素 并拓展为(val-a[pos-1]+a[pos],pos+1),拓展k次可得正确答案 状态数为2k+1;
例如:
对于状态(a[1],2)可拓展为(a[2],3)和(a[1]+a[2],3)避免了重复且排除了无用状态
时间复杂度O(KlogK)
80分代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int Maxn=1e6+50;
inline int read()
{
    char ch=getchar();int i=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
    return i*f;
}
struct node
{
    ll val;
    int pos;
    node(ll val,int pos):val(val),pos(pos){}
    friend inline bool operator <(const node &a,const node &b)
    {
        return a.val>b.val;
    }
};
priority_queue<node>q;
ll a[Maxn];
int n,k;
inline void readin()
{
    n=read(),k=read();
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);
}
inline void solve()
{
    q.push(node(a[1],2));
    for(int i=1;i<k;i++)
    {
        ll val=q.top().val;
        int nowpos=q.top().pos;
        q.pop();
        if(nowpos<=n)
        {
            q.push(node(val-a[nowpos-1]+a[nowpos],nowpos+1));
            q.push(node(val+a[nowpos],nowpos+1));
        }
    }
    printf("%lld",q.top().val);
}
int main()
{
    readin();
    solve();
}


剩下20%数据:二分答案以后折半搜索,时间复杂度O(2^{K/2}logW)

注意long long ...

100分代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int Maxn=1e7+50;
inline ll read()
{
    char ch=getchar();ll i=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
    return i*f;
}
struct node
{
    ll val;
    int pos;
    node(ll val,int pos):val(val),pos(pos){}
    friend inline bool operator <(const node &a,const node &b)
    {
        return a.val>b.val;
    }
};
priority_queue<node>q;
ll a[Maxn];
int n,cnta,cntb;
ll ansa[Maxn],ansb[Maxn],k;
inline void readin()
{
    n=read(),k=read();
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);
}
inline void solve()
{
    q.push(node(a[1],2));
    for(int i=1;i<k;i++)
    {
        ll val=q.top().val;
        int nowpos=q.top().pos;
        q.pop();
        if(nowpos<=n)
        {
            q.push(node(val-a[nowpos-1]+a[nowpos],nowpos+1));
            q.push(node(val+a[nowpos],nowpos+1));
        }
    }
    printf("%lld\n",q.top().val);
}
inline void Dfs1(int now,int max,int &cnt,ll sum)
{
    if(now>max){if(sum)ansa[++cnt]=sum;}
    else
    {
        Dfs1(now+1,max,cnt,sum+a[now]);
        Dfs1(now+1,max,cnt,sum);
    }
}
inline void Dfs2(int now,int max,int &cnt,ll sum)
{
    if(now>max){if(sum)ansb[++cnt]=sum;}
    else
    {
        Dfs2(now+1,max,cnt,sum+a[now]);
        Dfs2(now+1,max,cnt,sum);
    }
}
inline bool judge(ll val)
{
    ll ret=0;
    int j=1;
    for(int i=cnta;i>=0;i--)
    {
        if(ansa[i]>val)continue;
        for(;j<=cntb&&ansb[j]+ansa[i]<=val;j++);
        ret+=j;
    }
    return ret>k;
}
inline void solve2()
{
    Dfs1(1,n/2,cnta,0);
    Dfs2(n/2+1,n,cntb,0);
    sort(ansa+1,ansa+cnta+1);
    sort(ansb+1,ansb+cntb+1);
    ll l=0,r=0x3f3f3f3f3f3f3f3f,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(judge(mid))r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",r+1);
}
int main()
{
    readin();
    if(n>35)solve();
    else solve2();
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值