把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4408】[FJOI2016] 神秘数(主席树)

点此看题面

  • 给定一个长度为\(n\)的序列\(a_i\),进行\(m\)次询问。
  • 每次询问给出一个区间\([l,r]\),表示用\(a_{l\sim r}\)构成一个可重集。求不能用该可重集的子集的和表示的最小正整数。
  • \(n,m\le 10^5,\sum a_i\le10^9\)

暴力

首先,我们把\(a_{l\sim r}\)中的数抠出来存到一个数组\(s_{1\sim t}\)中,并将其从小到大排序。

枚举\(i\),显然只用前\(i-1\)个数能得到的最大的数为\(ans=\sum_{k=1}^{i-1}s_k\)

如果\(s_i\le ans+1\),说明\(s_i\)能够接上前面的答案,那么就继续枚举。

否则就输出\(ans+1\),然后\(break\)

优化

假设我们已知当前\(ans\),显然我们不用傻乎乎去暴力枚举后面的\(s_i\),而是可以直接将\(ans\)加上所有\(\le ans+1\)\(s_i\),然后再去继续处理新的\(ans\)

这一过程可以用值域线段树优化。

关于复杂度,因为每次\(ans\)差不多至少会翻倍,所有最多操作\(O(logn)\)次。

又由于是区间询问,只要用主席树就好了。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LN 20
using namespace std;
int n,dc,a[N+5],dv[N+5],Rt[N+5];
class ChairmanTree//主席树
{
	private:
		int Nt;struct node {int V,S[2];}O[N*LN+5];
	public:
		I void Ins(int& rt,CI lst,CI x,CI v,CI l=1,CI r=dc)//单点修改
		{
			if((O[rt=++Nt]=O[lst]).V+=v,l==r) return;RI mid=l+r>>1;
			x<=mid?Ins(O[rt].S[0],O[lst].S[0],x,v,l,mid):Ins(O[rt].S[1],O[lst].S[1],x,v,mid+1,r);
		}
		I int Q(CI x,CI y,CI L,CI R,CI l=1,CI r=dc)//区间查询
		{
			if(L<=l&&r<=R) return O[x].V-O[y].V;RI mid=l+r>>1;
			return (L<=mid?Q(O[x].S[0],O[y].S[0],L,R,l,mid):0)+(R>mid?Q(O[x].S[1],O[y].S[1],L,R,mid+1,r):0);
		}
}C;
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),dv[i]=a[i];
	for(sort(dv+1,dv+n+1),dc=unique(dv+1,dv+n+1)-dv-1,i=1;i<=n;++i)//离散化
		C.Ins(Rt[i],Rt[i-1],lower_bound(dv+1,dv+dc+1,a[i])-dv,a[i]);//建主席树
	RI Qt,x,y,t,k,ans;scanf("%d",&Qt);W(Qt--)
	{
		scanf("%d%d",&x,&y),ans=k=0;//k表示已经加到了离散化后为k的数
		W((t=upper_bound(dv+1,dv+dc+1,ans+1)-dv-1)^k) ans+=C.Q(Rt[y],Rt[x-1],k+1,t),k=t;//不断更新ans,每次加上尚未加过的所有小于等于ans+1的a[i]
		printf("%d\n",ans+1);//输出ans+1
	}return 0;
}
posted @ 2020-10-11 21:11  TheLostWeak  阅读(91)  评论(0编辑  收藏  举报