【积累】最小不能表示正整数 (以及一些做法
最小不能表示正整数
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);
}
}