zoukankan      html  css  js  c++  java
  • Luogu P2048 【NOI2010】 超级钢琴|堆+ST表

    题目链接
    题意:

    有一架超级钢琴,可弹 (n) 个音符,第 (i) 个音符权值 (a_i)。定义超级和弦为一段长度不小于 (L) 且不大于 (R) 的子序列,求前 (k) 大的超级和弦的权值之和。
    (n le 5 imes 10^5) , (k le 5 imes 10^5) , (-1000le a_i le 1000)
    题解:

    之前GDKOI2021提到过,可能是一个比较常见的trick?
    简而言之,这个trick是针对类似本题的“可伸缩区间问题”,对于每个点 (i) ,取以此为起点,长度为 (L)(R) 的序列中和最大的一段(记该结果为 ((x,L,R)) ,该结果需要记录长度(设为 (t_i)),权值,以及 (x) 本身等数据。需要注意的是,这里的 (L)(R) 随着找下一最大值的过程会发生改变,故需维护) 。将所有的 ((x,L,R)) 按权值由大到小的顺序放入堆中。取出一个 ((x,L,R)) 后,则加入 ((x,L,t_i-1)) 以及 ((x,t_i+1,R))。不难看出,这样取可以取出以 (i) 为起点的第二,第三大序列。以此类推。

    对于本题,剩下的就是维护最大段了。这显然不能暴力,注意到该序列为无修改的,故选择前缀和快速求和,通过码量较小的ST表维护 ((x,L,R)) 即可解决最大段的维护。具体来讲,ST表维护的是由 (1)(L)的前缀和 和 由 (1)(R) 之间的前缀和的最大值,查询时减去由 (1)(x-1) 的前缀和即可。

    上代码:

    #include<bits/stdc++.h>
    #define N 500500
    using namespace std;
    struct data
    {
    	int x,xx,num,L,R;
    }h[N*3];
    int n,k,l,r,a[N],f[N][22],fa[N][22];
    long long g,ans;
    bool cmp(data x,data y)
    {
    	return x.x<y.x;
    }
    int fin(int x,int L,int R)
    {
    	if (x+R-1>n) R=n-x+1;
    	if (L>R) return 0;
    	int c=0;
    	for (int i=1;i<=19;i++)
    	  if ((1<<i)>=R-L+1) {c=i-1;break;}
    	if (f[x+L-1][c]>=f[x+R-(1<<c)][c]) return fa[x+L-1][c]-x+1;
    	else return fa[x+R-(1<<c)][c]-x+1;
    }
    void add(int num,int x,int xx,int l,int r)
    {
    	g++;int fa=g/2,so=g;
    	h[g].x=x;h[g].xx=xx;h[g].num=num;h[g].L=l;h[g].R=r;
    	while (cmp(h[fa],h[so])&&fa)
    	{
    		swap(h[fa],h[so]);
    		so=fa;fa=fa/2;
    	}
    }
    data del()
    {
    	data tmd=h[1];
    	h[1]=h[g];g--;
    	int fa=1,so=2;
    	if (cmp(h[so],h[so+1])&&so+1<=g) so++;
    	while (cmp(h[fa],h[so])&&so<=g)
    	{
    		swap(h[fa],h[so]);
    		fa=so;so*=2;
    		if (cmp(h[so],h[so+1])&&so+1<=g) so++;
    	}
    	return tmd;
    }
    int main()
    {
    	cin>>n>>k>>l>>r;
    	for (int i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		f[i][0]=f[i-1][0]+a[i];fa[i][0]=i;
    	} 
    	for (int i=1;i<=19;i++)
    	{
    		for (int j=1;j<=n-(1<<i)+1;j++)
    		{
    		  if (f[j][i-1]>=f[j+(1<<(i-1))][i-1])	
    		  f[j][i]=f[j][i-1],fa[j][i]=fa[j][i-1];
    		  else f[j][i]=f[j+(1<<(i-1))][i-1],fa[j][i]=fa[j+(1<<(i-1))][i-1];
    		}
    	}//ST表维护$(x,L,R)$
    	for (int i=1;i<=n-l+1;i++)
    	{
    		int tmp=fin(i,l,r);
    		add(i,f[i+tmp-1][0]-f[i-1][0],tmp,l,r); 
    	}//初始化堆
    	for (int i=1;i<=k;i++)
    	{
    		data tmd=del();
    		ans+=tmd.x;
    		int tmp=fin(tmd.num,tmd.L,tmd.xx-1);
    		if (tmp) add(tmd.num,f[tmd.num+tmp-1][0]-f[tmd.num-1][0],tmp,tmd.L,tmd.xx-1);
    		tmp=fin(tmd.num,tmd.xx+1,tmd.R);
    		if (tmp) add(tmd.num,f[tmd.num+tmp-1][0]-f[tmd.num-1][0],tmp,tmd.xx+1,tmd.R);
    	}//维护存放$(x,L,R)$的堆	
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    SpringBoot多数据源启动器
    数据结构模拟器
    mysql5.7查询今天、昨天、本周、上周、本月、上月数据
    SpringBoot项目本地可以发送邮件,部署到阿里云服务器发送邮件失败的解决方法
    Centos7搭建Maven私服-Nexus3.19.1-01
    Linux中部署jar包并指定日志输出文件
    ThreadLocal是什么?谈谈你对他的理解
    leetcode-双指针遍历
    不要再纠结css/js/html有没有必要放在WEB-INF下了
    数据库的表的字段名称与实体类(pojo)不对应解决方案
  • 原文地址:https://www.cnblogs.com/fmj123/p/14901388.html
Copyright © 2011-2022 走看看