1. 程式人生 > 實用技巧 >題解 P4587 【[FJOI2016]神祕數】

題解 P4587 【[FJOI2016]神祕數】

P4587 [FJOI2016]神祕數

題意簡述:

一個可重複數字集合S的神祕數定義為最小的不能被S的子集的和表示的正整數。例如S={1,1,1,4,13}

現給定n個正整數a[1]..a[n],m個詢問,每次詢問給定一個區間l,r,求由a[l],a[l+1],…,a[r]所構成的可重複數字集合的神祕數。


解析

先探究神祕數的有關性質。

假設我們是把數一個一個插入序列中的(這也是主席樹的解題過程)

設當前能組成的數的值域為 \([1,x]\) ,要插入的數為 \(a_i\)

有如下性質:

  1. \(a_i>x+1\) 時,\(x+1\) 不能被組成,故此時神祕數為 \(x+1\)

  2. \(a_i<x+1\)

    時,能表示的值域變為 \([1,a_i+x]\)

若每插入一個 \(a_i\) 就新增一個版本,那麼對於每個版本:

設它的值域為 \([1,x]\),則答案 \(ans=x+1\)

此時檢查小於等於 \(ans\) 的所有數的和 \(sum\)

\(ans\leq sum\), 則一定有未選的且小於等於 \(ans\) 的數。

則令 \(ans = res+1\),重複檢查。

\(ans > sum\),則答案就是 \(ans\)

這裡維護的並不是權值線段樹,離散化什麼的也不用了。

code :

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1e5+10;

struct node
{
	int lson,rson;
	ll _sum;
} tree[N<<5];
int root[N],tot=0;

int n,m;
int ans=0,res=0;

#define lnode tree[node].lson
#define rnode tree[node].rson
#define DEFMID ll mid=start+end>>1;
#define lnode1 tree[node1].lson
#define rnode1 tree[node1].rson

int insert(int node,ll start,ll end,int x)
{
	int node1=++tot;
	tree[node1]=tree[node];
	tree[node1]._sum+=x;
	if(start==end)
	{
		return node1;
	}
	DEFMID
	if(x<=mid) lnode1=insert(lnode,start,mid,x);
	else rnode1=insert(rnode,mid+1,end,x);

	tree[node1]._sum=tree[lnode1]._sum+tree[rnode1]._sum;
	return node1;
}

ll query(int node1,int node,ll start,ll end,ll l,ll r)
{
	if(l<=start&&end<=r) return tree[node1]._sum-tree[node]._sum;
	DEFMID
	ll ans=0;
	if(l<=mid) ans+=query(lnode1,lnode,start,mid,l,r);
	if(r>mid) ans+=query(rnode1,rnode,mid+1,end,l,r);

	return ans;
}

int main()
{
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
	{
		int x;
		scanf("%d",&x);
		root[i]=insert(root[i-1],1,1e9,x);
	}

	scanf("%d",&m);
	for(int i=1; i<=m; i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		ans=1;
		while(1)
		{
			res=query(root[r],root[l-1],1,1e9,1,ans);
			if(res>=ans) ans=res+1;
			else break;
		}
		printf("%d\n",ans);
	}
	return 0;
}