BZOJ 4345 [POI2016]Korale

模拟搜索+线段树

我觉得这题挺妙的啊。

注意到当n=1000000很大的时候会有2^1000000种取法,但题目只要求选到k=1000000个,也就是我们不能爆搜,但要保证每一次都能取到一个前k大的。

也就是要进行一个优秀的搜索。考虑朴素的垃圾搜索,是每次枚举i选或者不选,然后搜索i+1。注意到搜索的下一层的和总大于上一层。因此把其中有用的节点拿出来,是一个树形结构,若当前在第i层,有两种决策:取i、放弃前一个取的数然后取i。显然这样只需维护当前搜索的最小的边界,用一个堆即可。

输出方案,考虑爆搜。注意到搜索的过程中每一次合法的临时答案都会在前k个答案里面出现,也就是如果能进行一个优秀的搜索我们就能只做O(k)次,同样考虑垃圾搜索,是每次暴力枚举下一个能放进来的,那么用线段树维护区间最小即可优化暴力枚举,从而实现优秀搜索。

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define N 1000005
#define mkp(_i,_j) make_pair(_i,_j)
using namespace std;
int in()
{
    char c = getchar(); int r = 0;
    for(; c < '0' || c > '9'; c = getchar());
    for(; c >='0' && c <='9'; r=r*10+c-'0', c = getchar());
    return r;
}
typedef long long ll;
typedef pair<ll,int> pii;
int n, k, a[N], b[N], cnt, t[N<<4]; ll f[N];
void build(int x, int l, int r)
{
    if(l == r) {t[x] = a[l]; return;} int mid = (l+r)>>1;
    build(x<<1,l,mid); build(x<<1|1,mid+1,r);
    t[x] = min(t[x<<1], t[x<<1|1]);
}
int query(int x, int l, int r, int ql, int qr, ll lim)
{
    if(t[x] > lim) return -1; int mid = (l+r)>>1;
    if(ql <= l && r <= qr)
    {
        if(l == r) return l;
        return t[x<<1] <= lim ? query(x<<1,l,mid,ql,qr,lim) : query(x<<1|1,mid+1,r,ql,qr,lim);
    }
    int ret = -1;
    if(ql <= mid) ret = query(x<<1,l,mid,ql,qr,lim);
    if(ret == -1 && mid < qr) ret = query(x<<1|1,mid+1,r,ql,qr,lim);
    return ret;
}
int sta[N], stacnt;
void dfs(int x, ll des)
{
    if(!des)
    {
        if(--cnt == 0)
        {
            for(int i = 1; i <= stacnt; i++) printf("%d%c",sta[i]," \n"[i==stacnt]);
            exit(0);
        }
        return;
    }
    for(; x <= n; x++)
    {
        x = query(1,1,n,x,n,des);
        if(x == -1) return;
        sta[++stacnt] = x;
        dfs(x+1, des-a[x]);
        --stacnt;
    }
}
int main()
{
//  freopen("data.in","r",stdin);
    n = in(), k = in();
    for(int i = 1; i <= n; i++) a[i] = b[i] = in();
    sort(b+1, b+1+n);
    priority_queue<pii,vector<pii>, greater<pii> > q;
    q.push(mkp(0,0));
    for(int i = 1; i <= k; i++)
    {
        pii p = q.top(); q.pop(); f[i] = p.first;
        if(p.second < n) 
        {
            q.push(mkp(p.first+b[p.second+1], p.second+1));
            if(p.second) q.push(mkp(p.first-b[p.second]+b[p.second+1], p.second+1));
        }
    }
    build(1,1,n);
    printf("%lld\n",f[k]); int cur = 1; for(; f[cur] != f[k]; cur++); cnt = k-cur + 1;
    dfs(1,f[k]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值