【題目描述】
HDU - 5919 Sequence II
【題目分析】
題目給定一個數組,每次查詢一個區間,找出區間內不同數字的個數x,然后輸出按出現順序第x/2向上取整個數字的位置。
- 按照要求,我們首先需要能夠找出給定區間不同的數字個數。
首先,我們分析一個簡單一些的問題:對于右端點固定的區間,如何計算不同左區間內不同數字的個數。
我們不妨用一個數組記錄cntcntcnt哪些位置出現了一個不同的數字,用sumsumsum數組進行維護cnt[1..l]cnt[1..l]cnt[1..l]的和(可以用線段樹或者樹狀數組),那么對于區間[l,r][l,r][l,r]內不同數字的個數就是sum[r]?sum[l?1]sum[r]-sum[l-1]sum[r]?sum[l?1]。
在從前往后進行添加的過程中,如果該數字在前面已經出現,就將前面的標記消除,在后面的位置進行標記,也就是說盡可能將標記后放。例如對于數組1,2,2,3,5,11,2,2,3,5,11,2,2,3,5,1維護以后的cntcntcnt數組就是0,0,1,1,1,10,0,1,1,1,10,0,1,1,1,1,這樣做的原因是我們假設的是右端點固定,對于重復的元素,在后面如果出現過前面就沒有必要標記。
如果詢問是離線的,我們大可以先將詢問保存下來,然后從前往后加入數據的過程中不斷將對應的詢問答案保存(對應是指右端點相同),最后輸出就可以了。
可是這個問題是強制在線的,所以我們必須使用主席樹進行可持久化。可是這種可持久化和以前的主席樹運用不同,因為在添加的過程中會將前面的標記消除,所以不同根節點的主席樹不在擁有可以互相加減的能力(加減的結果不再有意義)。然而在我們這個問題里面我們是不需要進行加減的。 - 不同于求區間第K大的時候我們的主席樹維護的是值區間,即值在區間內的個數,這里根節點的1..n1..n1..n指的是數據范圍aiaiai的最大值。在這里我們的根節點的1..n1..n1..n的nnn指的是數據的個數,就是題目中的nnn,標記的是該位置上出現了一個之前沒有出現過的數字。 因此對于每次詢問,我們訪問的版本里保存的就是實際的數組,直接計數就可以。而區間第K大就需要減去之前的版本才是該區間內的數的個數。
- 題目要求的是數據第一次出現的位置,可是按照上面的想法進行建樹的話我們保存的是當前區間[1,i][1,i][1,i]數據最后一次出現的位置。很自然,我們應該進行逆序建樹,這樣的話我們保存的就是[i,n][i,n][i,n]區間內數據第一次出現的位置,對于需要查詢的區間[l,r][l,r][l,r],我們訪問第lll個版本的主席樹,就滿足題目要求啦。
求出數字的個數后我們再除以2向上取整就是題目要求的k,然后在找出對應的位置,這里類似區間第K大
【參考文獻】
大佬博客1
大佬博客2
【AC代碼】
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<climits>
#include<string>
#include<cmath>
#include<cstdlib>using namespace std;const int MAXN=200005;
int a[MAXN];
int n,m;
struct node
{int ls,rs,cnt;
}tree[MAXN*40];
int root[MAXN*40];
int b[MAXN];
int tot;void Insert(int &now,int pre,int x,int l,int r,int add)
{int tmp=now; now=++tot;tree[now]=tmp?tree[tmp]:tree[pre];tree[now].cnt+=add;if(l==r) return;int mid=(l+r)>>1;if(x<=mid) Insert(tree[now].ls,tree[pre].ls,x,l,mid,add);else Insert(tree[now].rs,tree[pre].rs,x,mid+1,r,add);
}int GetSum(int k,int l,int r,int L,int R)
{if(l>=L && r<=R) return tree[k].cnt;int ret=0; int mid=(l+r)>>1;if(L<=mid) ret+=GetSum(tree[k].ls,l,mid,L,R);if(R>mid) ret+=GetSum(tree[k].rs,mid+1,r,L,R);return ret;
}int query(int k,int l,int r,int x)
{if(l==r) return l;int mid=(l+r)>>1;int tmp=tree[tree[k].ls].cnt;if(x<=tmp) query(tree[k].ls,l,mid,x);else query(tree[k].rs,mid+1,r,x-tmp);
}int main()
{int T,ans,l,r,tmp,sum;scanf("%d",&T);for(int Case=1;Case<=T;Case++){scanf("%d%d",&n,&m);memset(tree,0,sizeof(tree));memset(a,0,sizeof(a));memset(b,0,sizeof(b));memset(root,0,sizeof(root));tot=0;for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=n;i>0;i--){if(b[a[i]]) Insert(root[i],root[i+1],b[a[i]],1,n,-1);Insert(root[i],root[i+1],i,1,n,1);b[a[i]]=i;}ans=0;printf("Case #%d:",Case);//測試 //printf("\n");for(int i=0;i<m;i++){scanf("%d%d",&l,&r);l=(l+ans)%n+1; r=(r+ans)%n+1;if(l>r) tmp=l,l=r,r=tmp;//printf("test: l=%d r=%d\n",l,r);sum=GetSum(root[l],1,n,l,r);//printf("test: sum=%d\n",sum);sum=(sum+1)/2;//printf("test: sum=%d\n",sum);ans=query(root[l],1,n,sum);printf(" %d",ans);}printf("\n");}return 0;
}