【积累】最小不能表示正整数 (以及一些做法

最小不能表示正整数

1,题:little w and Exchange

题意:给定两个整数 \(n\)\(m\) ,以及一个长度为 \(n\) 的数组 \(A\) , 询问对于所有正整数 \(w\leq m\) ,是否都能用数组 \(A\) 中的数表示,即说对于所有正整数 \(w\leq m\) ,是否存在子数组 \(S\subseteq A\)\(S\) 内所有元素的和恰好等于 \(w\) 。是的话输出 YES ,否则输出 NO\(1\leq n\leq1000,\,1\leq m\leq2^{31}-1,\,1\leq a_i\leq2^{31}-1\)

解:对数组从小到大排序后,求前缀和,如果下一个数的数值 \(a_i>sum_{i-1}+1\) ,则最小不能表示数为 \(sum_{i-1}+1\) ,否则 \([1,sum_i]\) 的数均能由子数组 \(A_{1,2,\dots,i}\) 表示,需继续判断下一个数 。

证明:

假设该结论正确,则对于排序后的数组由: 子数组 \(A_{1,2,\dots,i}\) 表示任意值不超过 \(sum_{i-1}\) 的正整数。

当加入一个新的数 \(a_i\),如果这个数 \(a_i>sum_{i-1}+1\) ,显然 \(a_i\) 无法与任意零个或多个 \(a_j(j<i)\) 加和得到 \(sum_{i-1}+1\) 。反之,如果 \(a_i\leq sum_{i-1}+1\) ,则区间 \([sum_{i-1}+1,sum_i]\) 之内的正整数都能由 \(a_i\) 与任意零个或多个 \(a_j(j<i)\) 加和得到,因为 \(0\leq sum_{i-1}+1-a_i< sum_i-a_i\le sum_{i-1}\) 能被表示。

而当 \(i=1,sum_{i-1}=0\) ,显然只有 \(a_i=1\) ,才能表示 \(sum_{i-1}+1=1\) ,否则,最小不能表示数则为 \(1\)

结论正确。

假如生成一个最优序列,则当 \(n=31\) 时,就能表示出 \([1,2^{31}]\) 只能的所有正整数了。(考虑2进制数的01表示,当表示 \(2^{31}\) 内的数,只需要31位 )。

代码:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;

int a[maxn];
int main()
{
	int n,m,x;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	ll sum=0;
	for(int i=1;i<=n;i++)
		if(a[i]<=sum+1)sum+=a[i];
		else break;
	if(sum>=m)puts("YES");
	else puts("NO");
}

2,题:牛牛的凑数游戏

题意:给定两个正整数 \(n,m\) ,一个长度为 \(n\) 的数组 \(A\)\(m\) 个询问。对于每个询问的 \(l,r\) 回答在子数组 \(A[l,r]\) 中,最小不能表示正整数是什么?\(1\leq n,m\leq10^5,\,1\leq a_i\leq10^9,\,1\le l\le r\le n\)

解:对于每个询问,一开始设 \(sum=0\), 每次把区间 \([l,r]\) 内小于等于 \(sum+1\) 的数且尚未的数加进 \(sum\) ,如果 \(sum\) 的值在这次没有发生变化,则答案就为 \(sum+1\)总共需要迭代的次数其实仅为 \(log\,n\)

对于区间内的求值可以用数据结构来实现。

叨叨:这种乍一看瞧不出的时间复杂度,仔细一想确是很有道理。要习惯这种体型以及思维方式=w=。

代码:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int inf=0x3f3f3f3f;


int a[maxn];

ll h[maxn];int cnt;
inline int id(ll v){return lower_bound(h+1,h+1+cnt,v)-h;}


int T[maxn],L[maxn<<4],R[maxn<<4],tot;ll sum[maxn<<4];
void update(int&rt,int pre,int l,int r,int x,int v)
{
	rt=++tot;
	sum[rt]=sum[pre]+v;
	L[rt]=L[pre];R[rt]=R[pre];
	if(l==r)return;
	int mid=(l+r)>>1;
	if(x<=mid)update(L[rt],L[pre],l,mid,x,v);
	else update(R[rt],R[pre],mid+1,r,x,v);
}
ll query(int st,int ed,int l,int r,int ql,int qr)
{
	if(ql>r||qr<l||st==ed)return 0;
	if(ql<=l&&r<=qr)return sum[st]-sum[ed];
	int mid=(l+r)>>1;
	return query(L[st],L[ed],l,mid,ql,qr)+query(R[st],R[ed],mid+1,r,ql,qr);
}

int main()
{
	int n,m,l,r;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),h[++cnt]=a[i];
	sort(h+1,h+1+cnt);
	cnt=unique(h+1,h+1+cnt)-h-1;
	for(int i=1;i<=n;i++)update(T[i],T[i-1],1,cnt,id(a[i]),a[i]);
	ll sum,lsum;int k,lk;
	while(m--)
	{
		scanf("%d%d",&l,&r);
		sum=0;lsum=-1;lk=0;
		while(lsum!=sum)
		{
			lsum=sum;
			k=lower_bound(h+lk,h+1+cnt,sum+1)-h;
			if(k>cnt||h[k]>sum+1)k--;
			if(lk==k)break;
			sum+=query(T[r],T[l-1],1,cnt,lk+1,k);
			lk=k;
		}
		printf("%lld\n",sum+1);
	}
}
posted @ 2020-10-20 19:56  草丛怪  阅读(275)  评论(0编辑  收藏  举报