洛谷 P4587 [FJOI2016]神祕數
阿新 • • 發佈:2021-10-15
大鴿子 llmmkk 正在補8.3號咕掉的題
時隔兩個月,再看到這道題,我又是一臉懵,這種思維的培養太重要了
連結:
題意:
給出 \(n\) 個點的序列,\(m\) 次詢問區間神祕數。
神祕數定義為最小的不能被序列的子集的和表示的正整數。
如序列 \(\{1,1,4,1,13\}\) 的神祕數是 \(8\)。
分析:
這題重點在神祕數的求法,先考慮暴力求法,由於定義是序列子集,那麼首先考慮將該區間排序,可能會得到一些有用的性質。
假設當前能夠表示的區間是 \([1,sum]\),對於當前的數 \(x\) 。
- \(x>sum+1\),那麼答案就是 \(sum+1\)。
- \(x\leq sum+1\),那麼能夠表示的區間將會變成 \([1,sum+x]\),繼續考慮下一個數。
仔細想一下會發現這個暴力很對。
然後考慮優化,暴力是一個一個判斷並處理的,我們考慮一次性處理多個 \(x\)。於是正解就是:
假設當前能夠表示的區間是 \([1,sum]\),記區間內所有小於等於 \(sum\) 的 \(x\) 之和為 \(res\)。
- \(res+1>sum\),那麼將 \(sum\) 更新為 \(res+1\)
- \(res+1\leq sum\) ,那麼答案就是 \(sum+1\)
仔細想想這個東西,發現它成功做到了上面的優化,同時它的正確性也很對。因為這個做法實在太難以言傳了,所以可以再參考一下 這篇
時間複雜度分析:假如當前的神祕數為 \(s1\),下一個神祕數是 \(s2\),再下一個是 \(s3\),那麼從 \(s2\) 到 \(s3\) 相比從 \(s1\) 到 \(s2\) 多出來的 \(x\) 一定是大於 \(s1\) 的,所以 \(ans\) 是成倍增長的,於是這個做法的複雜度就是 \(O(\log\sum a)\)
演算法:
於是我們需要一個東西能夠維護區間內某個值域的數值之和。可以在每個位置維護一個從 \(1\) 到當前位置的權值線段樹,拉到主席樹上,然後兩個區間查詢相減就可以做到。
主席樹 \(O(n\log n)\),上面的演算法 \(O(\log\sum a)\)
程式碼:
#include <bits/stdc++.h>
using namespace std;
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int N=1e5+5;
int n,m;
int a[N],q[N],qn,w[N];
int bin(int key){
int l=1,r=qn,mid;
while(l<r){
mid=(l+r+1)>>1;
if(q[mid]<=key)l=mid;
else r=mid-1;
}
return l;
}
int rt[N];
int tot,sum[N<<5],lc[N<<5],rc[N<<5];
int newnode(){
++tot;
sum[tot]=lc[tot]=rc[tot]=0;
return tot;
}
void pushup(int p){
sum[p]=sum[lc[p]]+sum[rc[p]];
}
int fi_built(int l,int r){
int p=newnode();
if(l==r){return p;}
int mid=(l+r)>>1;
lc[p]=fi_built(l,mid);
rc[p]=fi_built(mid+1,r);
return p;
}
int built(int l,int r,int pre,int x,int d){
int now=newnode(),mid=(l+r)>>1;
sum[now]=sum[pre],lc[now]=lc[pre],rc[now]=rc[pre];
if(l==r){sum[now]+=d*q[l];return now;}
if(x<=mid)lc[now]=built(l,mid,lc[now],x,d);
else rc[now]=built(mid+1,r,rc[now],x,d);
pushup(now);
return now;
}
int query(int l,int r,int p,int ql,int qr){
if(ql>qr)return 0;
if(l>=ql&&r<=qr)return sum[p];
int mid=(l+r)>>1,res=0;
if(ql<=mid)res+=query(l,mid,lc[p],ql,qr);
if(qr>mid)res+=query(mid+1,r,rc[p],ql,qr);
return res;
}
signed main(){
n=in;
for(int i=1;i<=n;i++)
a[i]=in,q[i]=a[i];
sort(q+1,q+1+n);
qn=unique(q+1,q+1+n)-(q+1);
rt[0]=fi_built(1,n);
for(int i=1;i<=n;i++)
rt[i]=built(1,n,rt[i-1],bin(a[i]),1);
m=in;
for(int i=1;i<=m;i++){
int l=in,r=in;
int ans=1,tans=bin(ans);
int que=query(1,n,rt[r],1,tans)-query(1,n,rt[l-1],1,tans);
while(que+1>ans){
ans=que+1;
tans=bin(ans);
que=query(1,n,rt[r],1,tans)-query(1,n,rt[l-1],1,tans);
}
cout<<ans<<'\n';
}
return 0;
}