P4587 [FJOI2016] 神秘数
P4587 [FJOI2016] 神秘数
暑假zdy集训让我感到人类智慧的一题
虽然本题貌似希望考察主席树,但是我觉得这题的Trick还是太智慧了
[FJOI2016] 神秘数
题目描述
一个可重复数字集合
现给定长度为
提示
对于
solution:
首先我们来思考一个问题:
假设在先前的集合中
[1,lim]已经被证明可取,现在新加入一个数x:
当x>lim+1:
显然lim+1这个数不可取,所以x对于答案无贡献
当x<=lim+1:
显然[1,lim+x]全部可取
lim+1由x+(lim+1-x)取得:
lim+2由x+(lim+2-x)取得
lim+3由x+(lim+3-x)取得...
然而新的极限显然就是x+lim了
但是我们发现,如果每一次对一个lim进行拓展操作,那么这个时间复杂度将会变为
所以我们考虑对于多个符合x<=lim的x进行拓展操作:
先说结论:记所有满足
只有当
否则直接结束,答案为lim+1
证:
显然,lim一定是由一些
如果
又因为[1,lim]已经被取到,且sum[lim]中的每个数都满足x<=lim,
所以由之前朴素的转移不难想到:
将sum[lim]分成两部分,pre和suf
其中pre用来组成[1,lim]
然后suf中的每个数都对lim由贡献
那么对于suf中的每个数,他们对lim贡献完之后,新的lim显然就是
pre中所有数字的和加上suf中所有数字的和,既为sum
然后不难想到主席树在这题中的应用:计算sum[x]
然后这题就做完了
Code:
#include<bits/stdc++.h> const int N=1e5+5; const int inf=1e9; using namespace std; int n,m,cnt; int ls[N*4*20],rs[N*4*20],rt[N],ans[N]; int a[N],b[N]; struct Tree{ int val,cnt; }t[N*4*20]; int upd(int &x,int last,int l,int r,int k) { x=++cnt; t[x]=t[last]; ls[x]=ls[last]; rs[x]=rs[last]; t[x].val+=b[k]; if(l==r) { return x; } int mid=l+r>>1; if(k<=mid)ls[x]=upd(ls[x],ls[last],l,mid,k); else rs[x]=upd(rs[x],rs[last],mid+1,r,k); return x; } void query(int x,int y,int l,int r,int pos,int &res) { if(l==r) { res+=t[x].val-t[y].val; return ; } int mid=l+r>>1; if(mid<pos) { query(rs[x],rs[y],mid+1,r,pos,res); res+=t[ls[x]].val-t[ls[y]].val; } else query(ls[x],ls[y],l,mid,pos,res); } void work() { cin>>n; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+1+n); for(int i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+1+n,a[i])-(b+1)+1; upd(rt[i],rt[i-1],1,n,a[i]); } cin>>m; for(int i=1,l,r;i<=m;i++) { scanf("%d%d",&l,&r); int x=1; while(1) { int val=0,x_id= x<=b[n] ? lower_bound(b+1,b+1+n,x)-(b+1) : n; if(x<=b[n]&&b[x_id+1]==x)x_id++; query(rt[r],rt[l-1],1,n,x_id,val); if(x<=val)x=val+1; else break; } printf("%d\n",x); } } int main() { work(); return 0; }