P4587 [FJOI2016] 神秘数

P4587 [FJOI2016] 神秘数

暑假zdy集训让我感到人类智慧的一题
虽然本题貌似希望考察主席树,但是我觉得这题的Trick还是太智慧了

[FJOI2016] 神秘数

题目描述

一个可重复数字集合 S 的神秘数定义为最小的不能被 S 的子集的和表示的正整数。例如 S={1,1,1,4,13},有:1=12=1+13=1+1+14=45=4+16=4+1+17=4+1+1+1

8 无法表示为集合 S 的子集的和,故集合 S 的神秘数为 8

现给定长度为 n正整数序列 am 次询问,每次询问包含两个参数 l,r,你需要求出由 al,al+1,,ar 所组成的可重集合的神秘数。

提示

对于 100% 的数据点,1n,m105a109

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进行拓展操作,那么这个时间复杂度将会变为 n2

所以我们考虑对于多个符合x<=lim的x进行拓展操作:

先说结论:记所有满足ai<=xai的和为sumx

只有当sumx>lim时,lim可被更新为sumx

否则直接结束,答案为lim+1

证:

显然,lim一定是由一些ai<=lim的ai组成的
如果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;
}
posted @   liuboom  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示