【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;
}
待到再迷茫时回头望,所有脚印会发出光芒