• 【BZOJ4504】K个串(优先队列,可持久化线段树)


    Description

    兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。

    兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第k大的和是多少。

    Input

    第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和

    接下里一行n个数a_i,表示这个数字序列

    Output

    一行一个整数,表示第k大的和

    f ( i , j ) f(i,j) f(i,j) 表示以 i i i 为左端点, j j j 为右端点的子串和。

    对于每一个左端点 i i i,我们可以先把 f ( i , j ) f(i,j) f(i,j) 里面最大的那个放入优先队列(从大到小排序)中,接下来进行 k − 1 k-1 k1 次这样的操作:

    先取出队首并弹出,设其所对应的是 f ( a , b ) f(a,b) f(a,b),那么我们可以找到 f ( a , j ) f(a,j) f(a,j) 中比 f ( a , b ) f(a,b) f(a,b) 小的最大的那个,并把它加入队列。也就是说,找到以 a a a 为左端点的 f ( a , j ) f(a,j) f(a,j) 次大值,扔进优先队列。

    这样的 k − 1 k-1 k1 次操作结束后,队首就是我们要找的那个 k k k 大值了。

    至于为什么,自己模拟一下或自己想一想。

    现在的关键是如何维护最大值和次大值。

    n x t i nxt_i nxti 表示数 a i a_i ai i i i 之后出现的第一个位置,显然可以 O ( n ) O(n) O(n) 预处理。

    显然, f ( 1 ) f(1) f(1) 可以 O ( n ) O(n) O(n) 维护出来。

    考虑如何从 f ( 1 ) f(1) f(1) 转移出 f ( 2 ) f(2) f(2)

    1. j = 1 j=1 j=1 时, f ( 2 , j ) f(2,j) f(2,j) 不存在,设为 − ∞ -infty

    2. 2 ≤ j < n x t 1 2leq j < nxt_1 2j<nxt1 时,如下图:

      在这里插入图片描述
      n x t 1 = 5 nxt_1=5 nxt1=5,不妨设 a 1 = a 5 = 10 a_1=a_5=10 a1=a5=10,那么由 n x t nxt nxt 的定义可知,当 2 ≤ j < n x t 1 2leq j < nxt_1 2j<nxt1 时, a 2 ∼ a j a_2sim a_j a2aj 中不可能存在一个数是 10 10 10。那么从 f ( 1 , j ) f(1,j) f(1,j) 更新到 f ( 2 , j ) f(2,j) f(2,j) 时,要减去 10 10 10 f ( 2 , j ) f(2,j) f(2,j) 的贡献。

      比如当 j = 4 j=4 j=4 时, a 1 ∼ a 4 a_1sim a_4 a1a4 中出现的数有: 1 1 1 2 2 2 10 10 10,所以 f ( 1 , 4 ) = 1 + 2 + 10 = 13 f(1,4)=1+2+10=13 f(1,4)=1+2+10=13(如图红框),而 a 2 ∼ a 4 a_2sim a_4 a2a4 a 1 = 10 a_1=10 a1=10 被去掉了,而 a 2 ∼ a 4 a_2sim a_4 a2a4 中又没有重新出现过 10 10 10,所以 f ( 2 , 4 ) = f ( 1 , 4 ) − 10 = 3 f(2,4)=f(1,4)-10=3 f(2,4)=f(1,4)10=3(如图蓝框)。

    3. j ≥ n x t 1 jgeq nxt_1 jnxt1 时,如下图:
      在这里插入图片描述
      我们用同样的一个例子。观察发现:从 f ( 1 , j ) f(1,j) f(1,j) 转移到 f ( 2 , j ) f(2,j) f(2,j) 的时候只是去掉了 a 1 = 10 a_1=10 a1=10,但是当 j ≥ n x t 1 jgeq nxt_1 jnxt1 时,可以保证在 a 2 ∼ a j a_2sim a_j a2aj 之间肯定会出现 10 10 10,所以 10 10 10 还是对 f ( 2 , j ) f(2,j) f(2,j) 有贡献,也就是说始终有 f ( 2 , j ) = f ( 1 , j ) f(2,j)=f(1,j) f(2,j)=f(1,j)

      比如当 j = 7 j=7 j=7 时, a 1 ∼ a 7 a_1sim a_7 a1a7 出现的数有 1 1 1 2 2 2 4 4 4 10 10 10 f ( 1 , 7 ) = 1 + 2 + 4 + 10 = 17 f(1,7)=1+2+4+10=17 f(1,7)=1+2+4+10=17,而 a 2 ∼ a 7 a_2sim a_7 a2a7 中出现的数和 a 1 ∼ a 7 a_1sim a_7 a1a7 中出现的数没有变化,所以 f ( 2 , 7 ) = f ( 1 , 7 ) = 17 f(2,7)=f(1,7)=17 f(2,7)=f(1,7)=17

    综上所述,可以推出:

    j < i − 1 j< i-1 j<i1 f ( i , j ) = f ( i − 1 , j ) = − ∞ f(i,j)=f(i-1,j)=-infty f(i,j)=f(i1,j)=

    j = i − 1 j=i-1 j=i1 时, f ( i , j ) = − ∞ f(i,j)=-infty f(i,j)=

    i ≤ j < n x t i − 1 i leq j < nxt_{i-1} ij<nxti1 时, f ( i , j ) = f ( i − 1 , j ) − a i − 1 f(i,j)=f(i-1,j)-a_{i-1} f(i,j)=f(i1,j)ai1

    j ≥ n x t i − 1 jgeq nxt_{i-1} jnxti1 时, f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i1,j)

    我们就可以发现, f ( i − 1 , j ) f(i-1,j) f(i1,j) f ( i , j ) f(i,j) f(i,j) 有一段区间是一样的( j < i − 1 j<i-1 j<i1 j ≥ n x t i − 1 jgeq nxt_{i-1} jnxti1),有一段区间是 f ( i , j ) f(i,j) f(i,j) 等于 f ( i − 1 , j ) f(i-1,j) f(i1,j) 减去一个相同的值( i ≤ j < n x t i − 1 i leq j < nxt_{i-1} ij<nxti1),有一个特殊的位置是 f ( i , j ) = − ∞ f(i,j)=-infty f(i,j)= j = i − 1 j=i-1 j=i1)。

    容易联想到用可持久化线段树维护 f ( i , j ) f(i,j) f(i,j)。( n n n 棵线段树,每棵线段树 i i i 存储 f ( i , j ) f(i,j) f(i,j) 及它们的最大值)。

    代码及注释如下:

    #include<bits/stdc++.h>
    
    #define N 100010
    #define lc t[u].ch[0]
    #define rc t[u].ch[1]
    #define ll long long
    #define int long long
    #define LNF 1e15
    
    using namespace std;
    
    inline int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<1)+(x<<3)+(ch^'0');
    		ch=getchar();
    	}
    	return x*f;
    }
    
    struct data
    {
    	ll val;
    	int rt,id;
    	data(){};
    	data(ll vall,int rtt,int idd){val=vall,rt=rtt,id=idd;}
    	bool operator < (const data &a) const
    	{
    		return val<a.val;
    	}
    };
    
    struct Segment_Tree
    {
    	int ch[2],where;
    	ll maxn,tag;
    }t[N*100];
    
    int n,nn,k,a[N],b[N],last[N],nxt[N];
    int node,root[N];
    ll f[N];
    bool vis[N];
    
    priority_queue<data>q;
    
    void up(int u)
    {
    	if(t[lc].maxn-t[lc].tag>t[rc].maxn-t[rc].tag)
    	{
    		t[u].maxn=t[lc].maxn-t[lc].tag;
    		t[u].where=t[lc].where;
    	}
    	else 
    	{
    		t[u].maxn=t[rc].maxn-t[rc].tag;
    		t[u].where=t[rc].where;
    	}
    }
    
    void build(int &u,int l,int r)
    {
    	u=++node;
    	if(l==r)
    	{
    		t[u].maxn=f[l];
    		t[u].where=l;
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(lc,l,mid);
    	build(rc,mid+1,r);
    	up(u);
    }
    
    void update(int &u,int last,int l,int r,int ql,int qr,ll x)
    {
    	u=++node;
    	t[u]=t[last];
    	if(ql<=l&&r<=qr)
    	{
    		t[u].tag+=x;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(ql<=mid) update(lc,t[last].ch[0],l,mid,ql,qr,x);
    	if(qr>mid) update(rc,t[last].ch[1],mid+1,r,ql,qr,x);
    	up(u);
    }
    
    void change(int u,int l,int r,int x,ll y)
    {
    	if(l==r)
    	{
    		t[u].maxn=y;
    		t[u].tag=0;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(x<=mid) change(lc,l,mid,x,y);
    	else change(rc,mid+1,r,x,y);
    	up(u);
    }
    
    signed main()
    {
    	n=read(),k=read();
    	for(int i=1;i<=n;i++) 
    		b[i]=a[i]=read();
    	sort(b+1,b+n+1);
    	nn=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;i++)
    		a[i]=lower_bound(b+1,b+nn+1,a[i])-b;
    	//以上是离散化
    	for(int i=1;i<=n;i++) nxt[i]=n+1;
    	for(int i=1;i<=n;i++)
    	{
    		if(last[a[i]]) nxt[last[a[i]]]=i;
    		last[a[i]]=i;
    	}
    	//以上是预处理nxt
    	for(int i=1;i<=n;i++)
    	{
    		if(!vis[a[i]])
    		{
    			vis[a[i]]=1;
    			f[i]=f[i-1]+b[a[i]];
    		}
    		else f[i]=f[i-1];
    	}
    	//预处理f[1][i]
    	build(root[1],1,n);//建出第一棵线段树
    	for(int i=2;i<=n;i++)
    	{
    		if(i<=nxt[i-1]-1) update(root[i],root[i-1],1,n,i,nxt[i-1]-1,b[a[i-1]]);
    		else update(root[i],root[i-1],1,n,1,n,0);//区间减
    		update(root[i],root[i],1,n,i-1,i-1,LNF);//单点修改
    		//注意这个update不能直接在原树上改,要新建一棵树,否则有可能修改到root[i-1]的树
    	}
    	for(int i=1;i<=n;i++)
    		q.push(data(t[root[i]].maxn+t[root[i]].tag,root[i],t[root[i]].where));//先把f[i]的最大值扔进队列
    	for(int i=1;i<k;i++)
    	{
    		data now=q.top();
    		q.pop();//取出队首
    		update(now.rt,now.rt,1,n,now.id,now.id,LNF);
    		q.push(data(t[now.rt].maxn+t[now.rt].tag,now.rt,t[now.rt].where));//放入次大值
    	}
    	printf("%lld
    ",q.top().val);//操作k-1次后直接输出
    	return 0;
    }
    /*
    7 3
    3 -2 1 2 2 1 3
    */
    
  • 相关阅读:
    bat批处理脚本学习系列(一)
    遇到的bug
    util.js 积累的一些基础函数代码
    session过期跳转到登陆页面并解决跳出iframe问题
    Centos7下的rabbitmq-server-3.8.11安装配置
    简单梳理 ES6 函数
    博客目录与学习计划
    DDD中聚合、聚合根的含义以及作用
    DDD中实体与值对象是干什么的
    DDD中限界上下文与通用语言的作用
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448660.html
Copyright © 2020-2023  润新知