【BZOJ-4408】神秘数 可持久化线段树

4408: [Fjoi 2016]神秘数

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 475  Solved: 287
[Submit][Status][Discuss]

Description

一个可重复数字集合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[1]..a[n],m个询问,每次询问给定一个区间[l,r](l<=r),求由a[l],a[l+1],…,a[r]所构成的可重复数字集合的神秘数。

Input

第一行一个整数n,表示数字个数。
第二行n个整数,从1编号。
第三行一个整数m,表示询问个数。
以下m行,每行一对整数l,r,表示一个询问。

Output

对于每个询问,输出一行对应的答案。

Sample Input

5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5

Sample Output

2
4
8
8
8

HINT

对于100%的数据点,n,m <= 100000,∑a[i] <= 10^9

Source

鸣谢yyh上传

Solution

这道题挺好的思路。

首先考虑在集合中已经选出$k$个数的时候,再加入第$k+1$个数的情况。

显然有当$a_{k+1}>\sum ^{k}_{i=1} a_{k} +1$时,$ans=\sum ^{k}_{i=1} a_{k}+1$

否则显然这个这些数能组合出的范围扩大$a_{k+1}$

所以思路就是对于一个$ans$,求出$\sum ^{R}_{i=L} (a_{i}<ans) a_{i}$,如果这些数能组合到$ans$,那么这个$ans$只能扩大,所以把$ans$扩大到$\sum ^{R}_{i=L} (a_{i}<ans) a_{i} +1$继续做,否则得到神秘数。

所以支持这样做的还是利用可持久化线段树求出。

但是这样的复杂度还是比较暴力的,不过题目中说了$\sum a_{i}<10^{9}$所以复杂度最坏是 $O(MlogNlog10^{9})$

话说这题被xyx秒了....

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while (ch<‘0‘ || ch>‘9‘) {if (ch==‘-‘) f=-1; ch=getchar();}
	while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘; ch=getchar();}
	return x*f;
}
#define MAXN 100010

int N,M,a[MAXN];

namespace PrTree{
	int root[MAXN],sum[MAXN*20],lson[MAXN*20],rson[MAXN*20],sz;
	inline void Insert(int l,int r,int &x,int last,int pos,int val)
	{
		x=++sz;
		lson[x]=lson[last],rson[x]=rson[last];
		sum[x]=sum[last]+val;
		if (l==r) return;
		int mid=(l+r)>>1;
		if (pos<=mid) Insert(l,mid,lson[x],lson[last],pos,val);
			else Insert(mid+1,r,rson[x],rson[last],pos,val);
	}
	inline int Query(int l,int r,int L,int R,int x,int y)
	{
		if (L>R) return 0;
		if (L<=l && R>=r) return sum[y]-sum[x];
		int mid=(l+r)>>1,re=0;
		if (L<=mid) re+=Query(l,mid,L,R,lson[x],lson[y]);
		if (R>mid) re+=Query(mid+1,r,L,R,rson[x],rson[y]);
		return re;
	}
}using namespace PrTree;

int ls[MAXN];

int main()
{
	N=read();
	for (int i=1; i<=N; i++) ls[i]=a[i]=read();

	sort(ls+1,ls+N+1); int tot=unique(ls+1,ls+N+1)-ls-1;

	for (int i=1; i<=N; i++) a[i]=lower_bound(ls+1,ls+tot+1,a[i])-ls;

	for (int i=1; i<=N; i++) PrTree::Insert(1,tot,root[i],root[i-1],a[i],ls[a[i]]);

	M=read();
	while (M--) {
		int l=read(),r=read();
		int ans=1,up,pos;
		while (1) {
			pos=upper_bound(ls+1,ls+tot+1,ans)-ls-1;
			if (ans<=(up=PrTree::Query(1,tot,1,pos,root[l-1],root[r])))
				ans=up+1;
			else break;
		}
		printf("%d\n",ans);
	}
	return 0;
}

  

时间: 2024-10-24 20:29:00

【BZOJ-4408】神秘数 可持久化线段树的相关文章

Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 128[Submit][Status][Discuss] Description 一个可重复数字集合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的子集的

BZOJ 3524 [Poi2014]Couriers 可持久化线段树

题意:链接 方法:可持久化线段树. 解析: 可持久化数据结构好神啊,感觉都好玄妙的感觉. 首先建树的目的就是建立一棵权值树,维护的是在L,R里某些权值的数的出现个数.然后呢,对于1~n每个节点建一棵树,并且是基于前一棵树的基础上的.然后对于每一次的新值我们只需要update一次,并且连接一下原来的树? 神犇们不是说这种结构就是一堆线段树连啊连就出来了吗. 查询的时候呢?有一些小改变,据说是以二分为基础的查询. 神犇们发明这种数据结构的时候,就发现了这种数据结构里的所有线段树是可以相减的这种性质,

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样

bzoj 2653 middle (可持久化线段树)

middle Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1981  Solved: 1097[Submit][Status][Discuss] Description 一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整.给你一个 长度为n的序列s.回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数. 其中a<b<c<d.位置也从0开始标号

BZOJ 4408 神秘数

题解同各神犇的方法... #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 100500 using namespace std; int n,a[maxn],b[maxn],m,l,r,len,tot=0,regis; int ls[maxn*20],rs[maxn*20],sum[maxn*20],root[maxn]; void

[BZOJ 3218] A + B Problem 【可持久化线段树 + 网络流】

题目连接:BZOJ - 3218 题目分析 题目要求将 n 个点染成黑色或白色,那么我们可以转化为一个最小割模型. 我们规定一个点 i 最后属于 S 集表示染成黑色,属于 T 集表示染成白色,那么对于每个点 i 就要连边 (S, i, B[i]) 和 (i, T, W[i]). 这样,如果一个点属于 S 集,就要割掉与 T 相连的边,就相当于失去了染成白色的收益. 我们再来考虑 “奇怪的点”,一个点 i 变成奇怪的点的条件是:i 是黑色且存在一个白色点 j 满足 j < i && L

[BZOJ 3207] 花神的嘲讽计划Ⅰ【Hash + 可持久化线段树】

题目链接:BZOJ - 3207 题目分析 先使用Hash,把每个长度为 k 的序列转为一个整数,然后题目就转化为了询问某个区间内有没有整数 x . 这一步可以使用可持久化线段树来做,虽然感觉可以有更简单的做法,但是我没有什么想法... 代码 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #inclu

BZOJ 4026 dC Loves Number Theory 分块+十字链表/可持久化线段树

题目大意:给定一个序列,多次询问某段区间乘积的φ值对1000777的模 我竟然卡过去了233333 将序列分块,记录fi,j表示第i块左端点到第j个点中出现的所有质数p的p?1p之积 每次询问[x,y],首先取出[x,y]区间内所有数的积,然后乘上fst,y(其中st是x后面第一个块端点所在块) 现在还剩[x,l[st]]部分没有统计 由于106以内的数分解得到的不同的质因数最多只有7个,因此暴力枚举零碎部分的质数即可 现在对于每个质数我们需要判断是否出现过 我们只需要判断这个质数下一次出现的位

HDU 4417.Super Mario-无修改区间小于等于H的数的个数-可持久化线段树

Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 9618    Accepted Submission(s): 4074 Problem Description Mario is world-famous plumber. His “burly” figure and amazing jumping ability