bzoj-2006 超级钢琴

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个长度为n的序列,取序列中L<=长度<=R的子串;

问这样的子串前k大个的和是多少;


题解:

好像是很神的题。。

首先考虑一个子串的和是什么?比如[i,j]这个串,和就是sum[j]-sum[i-1]   (sum表示前缀和);

那么对于每一个j的答案,应该是sum[i-1]比较小的i值;

怎么找到前k大,就用到了A*算法;

我们首先将每个j对应的最小的sum[i-1]求值,之后插入堆中;

每次拿出最大的;

因为这里每个j对应的i-1都相对自己最优,所以这里拿出的一定是最大的;

而拿出最大的之后,同时也要将这个j对应的下一个小的sum[i-1]插入;

这样的思想就是A*,预估值严格的A*算法经常可以用来解决这样的K大值问题吧;

查询区间第k大用主席树实现,我还去学了指针版的诸多姿势;

时间复杂度O(nlogn+klogn);


代码:


#include<queue>
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 510000
#define INF 500000000
using namespace std;
struct node
{
	node *l,*r;
	int sum;
	node ();
}*null=new node(),*root[N];
node:: node ()
{
	l=r=null,sum=0;
}
struct seg
{
	int val,R,k;
};
int sum[N];
bool operator <(seg a,seg b)
{
	return a.val<b.val;
}
priority_queue<seg>q;
void Insert(int l,int r,node* &p,int val)
{
	node *rt=new node();
	*rt=*p,p=rt;
	p->sum++;
	if(l==r)	return ;
	int mid=l+r>>1;
	if(val<=mid)	Insert(l,mid,rt->l,val);
	else			Insert(mid+1,r,rt->r,val);
}
int query(int l,int r,node *nol,node *nor,int k)
{
	if(l==r)
		return l;
	int mid=l+r>>1;
	if(k<=nor->l->sum-nol->l->sum)
		return query(l,mid,nol->l,nor->l,k);
	else
		return query(mid+1,r,nol->r,nor->r,k-nor->l->sum+nol->l->sum);
}
int main()
{
	int n,m,i,j,k,L,R,x,y;
	long long ans;
	scanf("%d%d%d%d",&n,&k,&L,&R);
	root[0]=null->l=null->r=null;
	null->sum=0;
	Insert(-INF,INF,root[0],0);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&x);
		sum[i]=sum[i-1]+x;
		root[i]=root[i-1];
		Insert(-INF,INF,root[i],sum[i]);
	}
	seg now;
	for(i=L;i<=n;i++)
	{
		now.R=i,now.k=1;
		now.val=sum[i]-query(-INF,INF,i-R-1>=0?root[i-R-1]:null,root[i-L],1);
		q.push(now);
	}
	for(i=1,ans=0;i<=k;i++)
	{
		now=q.top(),q.pop();
		ans+=now.val;
		now.k++;
		if(L+now.k-1<=R&&now.k<=now.R)
		{
			now.val=sum[now.R]-query(-INF,INF,now.R-R-1>=0?root[now.R-R-1]:null,root[now.R-L],now.k);
			q.push(now);
		}
	}
	printf("%lld\n",ans);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值