4299: Codechef FRBSUM

Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 756 Solved: 486
[Submit][Status][Discuss]
Description
数集S的ForbiddenSum定义为无法用S的某个子集(可以为空)的和表示的最小的非负整数。
例如,S={1,1,3,7},则它的子集和中包含0(S’=∅),1(S’={1}),2(S’={1,1}),3(S’={3}),4(S’={1,3}),5(S’ = {1, 1, 3}),但是它无法得到6。因此S的ForbiddenSum为6。
给定一个序列A,你的任务是回答该数列的一些子区间所形成的数集的ForbiddenSum是多少。
Input
输入数据的第一行包含一个整数N,表示序列的长度。
接下来一行包含N个数,表示给定的序列A(从1标号)。
接下来一行包含一个整数M,表示询问的组数。
接下来M行,每行一对整数,表示一组询问。
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%的数据,1≤N,M≤100000,1≤A_i≤109,1≤A_1+A_2+…+A_N≤109。


我们对于每一个区间试想:
如果当前我们可以凑出 [ 0 , x ],那么小于等于x+1的值,都会对答案造成贡献值。所以每次我们把答案小于x+1的所有值算出来,如果大于x,就更新右区间,否则已经无法更新了,直接退出即可。

然后每次算时,类似于斐波那契,速度是log的。

然后每个区间的权值,用主席树维护一下即可。


AC代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,a[N],b[N],m,q,rt[N],cnt;
struct node{
	int l,r,sum;
}t[N*20];
void change(int l,int r,int &x,int y,int k){
	x=++cnt; t[x]=t[y]; t[x].sum+=b[k];
	if(l==r)	return ; int mid=l+r>>1;
	if(k<=mid)	change(l,mid,t[x].l,t[y].l,k);
	else	change(mid+1,r,t[x].r,t[y].r,k);
}
int ask(int l,int r,int x,int y,int k){
	if(r==l)	return (t[x].sum-t[y].sum)*(k>=l);
	int mid=l+r>>1;
	if(k<=mid)	return ask(l,mid,t[x].l,t[y].l,k);
	else	return t[t[x].l].sum-t[t[y].l].sum+ask(mid+1,r,t[x].r,t[y].r,k);
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)	scanf("%lld",&a[i]),b[i]=a[i];
	sort(b+1,b+1+n);	m=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++)	a[i]=lower_bound(b+1,b+1+m,a[i])-b;
	for(int i=1;i<=n;i++)	change(1,m,rt[i],rt[i-1],a[i]); 
	cin>>q;
	while(q--){
		int x,y,res=0;	scanf("%lld %lld",&x,&y);
		while(1){
			int id=lower_bound(b+1,b+1+m,res+1)-b;
			if(b[id]!=res+1)	id--; 
			int t=ask(1,m,rt[y],rt[x-1],id);
			if(t<=res)	break;
			else	res=t;
		}
		printf("%lld\n",res+1);
	}
	return 0;
}