[bzoj4345][线段树][优先队列]Korale

39 篇文章 0 订阅

Description

有n个带标号的珠子,第i个珠子的价值为a[i]。现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的价值和。现在给所有可能的项链排序,先按权值从小到大排序,对于权值相同的,根据所用珠子集合的标号的字典序从小到大排序。请输出第k小的项链的价值,以及所用的珠子集合。

Input

第一行包含两个正整数n,k(1<=n<=1000000,1<=k<=min(2^n,1000000))。
第二行包含n个正整数,依次表示每个珠子的价值ai

Output

第一行输出第k小的项链的价值。 第二行按标号从小到大依次输出该项链里每个珠子的标号。

Sample Input

4 10

3 7 4 3

Sample Output

10

1 3 4

题解

妙啊…
尝试模拟爆搜的过程
把数组从小到大排序
设二元组 ( s u m , i ) (sum,i) (sum,i)表示当前总和为 s u m sum sum,最后一个数为 i i i
可以转移到 ( s u m − a [ i ] + a [ i + 1 ] , i ) (sum-a[i]+a[i+1],i) (suma[i]+a[i+1],i) ( s u m + a [ i + 1 ] , i ) (sum+a[i+1],i) (sum+a[i+1],i)
用一个堆模拟上述过程
由于每次加入的 s u m sum sum单调不减
所以模拟K次后得到的答案就是第K大的
记第K大的二元组共有m个
在原数组中进行构造
设 (答案-当前已经添加的数)=limit
根据上面模拟的过程 显然我们加的数单调不减
设最后一个数的位置是L,显然我们要在[L+1,n]这段区间里找到不比他小的数来爆搜
用线段树把他们一个个提出来就可以了…
每次加入后得到的数不超过limit
所以总状态数不超过K
复杂度 K log ⁡ n K\log n Klogn

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define lc now<<1
#define rc now<<1|1
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
inline void print(int x){write(x);printf(" ");}
int mn[4110000],a[1110000],b[1110000];
void build(int now,int l,int r)
{
	if(l==r){mn[now]=a[l];return ;}
	int mid=(l+r)/2;
	build(lc,l,mid);build(rc,mid+1,r);
	mn[now]=min(mn[lc],mn[rc]);
}
int findpos(int now,int l,int r,LL u)
{
	if(l==r)return l;
	int mid=(l+r)/2;
	if(mn[lc]<=u)return findpos(lc,l,mid,u);
	else return findpos(rc,mid+1,r,u);
}
int lpos;
int query(int now,int l,int r,LL u)
{
	if(r<lpos||mn[now]>u)return -1;
	if(l>=lpos)return findpos(now,l,r,u);
	int mid=(l+r)/2;
	int tmp=query(lc,l,mid,u);
	if(tmp!=-1)return tmp;
	return query(rc,mid+1,r,u);
}
priority_queue<pair<LL,int>,vector<pair<LL,int> >,greater<pair<LL,int> > > q;
int n,m,now,ct,sta[1110000],tp;
void dfs(LL limit,int L)
{
	if(!limit){now++;return ;}
	while(1)
	{
		lpos=L;if(lpos>n)break;
		int ta=query(1,1,n,limit);
		if(ta==-1)break;
		sta[++tp]=ta;L=ta+1;
		dfs(limit-a[ta],L);
		if(now==ct)return;
		tp--;
	}
}
int main()
{
	n=read();m=read();
	if(m==1){puts("0");return 0;}m--;
	for(int i=1;i<=n;i++)b[i]=a[i]=read();
	build(1,1,n);
	sort(b+1,b+1+n);
	LL s=-1;
	q.push(mp(b[1],1));
	for(int i=1;i<=m;i++)
	{
		LL s1=q.top().first;int pa=q.top().second;q.pop();
		if(s1!=s)s=s1,ct=1;
		else ct++;
		if(pa!=n)q.push(mp(s1+b[pa+1],pa+1)),q.push(mp(s1-b[pa]+b[pa+1],pa+1));
	}
	dfs(s,1);
	printf("%lld\n",s);
	for(int i=1;i<=tp;i++)print(sta[i]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值