P4587 [FJOI2016] 神秘数
P4587 [FJOI2016] 神秘数
暑假zdy集训让我感到人类智慧的一题
虽然本题貌似希望考察主席树,但是我觉得这题的Trick还是太智慧了
[FJOI2016] 神秘数
题目描述
一个可重复数字集合 \(S\) 的神秘数定义为最小的不能被 \(S\) 的子集的和表示的正整数。例如 \(S=\{1,1,1,4,13\}\),有:\(1 = 1\),\(2 = 1+1\),\(3 = 1+1+1\),\(4 = 4\),\(5 = 4+1\),\(6 = 4+1+1\),\(7 = 4+1+1+1\)。
\(8\) 无法表示为集合 \(S\) 的子集的和,故集合 \(S\) 的神秘数为 \(8\)。
现给定长度为 \(n\) 的正整数序列 \(a\),\(m\) 次询问,每次询问包含两个参数 \(l,r\),你需要求出由 \(a_l,a_{l+1},\cdots,a_r\) 所组成的可重集合的神秘数。
提示
对于 \(100\%\) 的数据点,\(1\le n,m\le {10}^5\),\(\sum a\le {10}^9\)。
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进行拓展操作,那么这个时间复杂度将会变为 \(n^2\)
所以我们考虑对于多个符合x<=lim的x进行拓展操作:
先说结论:记所有满足\(a_i<=x\)的\(a_i\)的和为\(sum_x\)
只有当\(sum_x>lim\)时,lim可被更新为\(sum_x\)
否则直接结束,答案为lim+1
证:
显然,lim一定是由一些\(a_i\)<=lim的\(a_i\)组成的
如果\(sum[lim] <= lim\) ,说明lim+1不可能被取到
又因为[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;
}

浙公网安备 33010602011771号