樹狀數組 + 二分
這裏先給出一個公式 第 i 個數的編號= i 之前比 i 小的總數量+ i 之後比 i 小的總數量+1
例如 1 4 3 6 2 5 這個序列,我們可以用這個序列來驗證上述的公式,比如 3 = 1 (3前邊只有1比3小,所以只有1個) + 1 (3後邊只有2比3小,所以只有1個) + 1,對吧。
已經找出來的編號我們可以存進樹狀數組中,這樣我們就可以直接通過getsum(x)直接查詢比x小的數總共有多少個數。
我們從後往前推,最後一個數可以直接推出來,它就等於前邊比他小的數+1(因爲他後面沒有數就可以不用加了 笑哭臉.jpg)
倒數第二個數呢, ?(他的編號,還不知道,所有先用問號代替)=num[ n-1 ](他前面比他小的數,這個數題目給了的)+ ?(他後面比他小的數,同樣也是不知道)+1,雖然我們現在不知道他的編號是多少,但我們可以通過二分思想把他的編號找出來,可以看下下面代碼
l=1;r=n+1; while(l<r){//二分思想確定下標 mid=(l+r)>>1; if(mid>getsum(mid-1)+num[i]+1)//數>(後面小於次數的人數)+(前面小於次數對的人數,也就是num[i])+1 r=mid;//此編號偏大 else l=mid+1;//此編號偏小 } change(r-1);//找出編號,編號等於r-1 ans[i]=r-1; flag[r-1]=true;
這樣就可以求出倒數第二個數,其實這個也可以求出倒數第一數,因爲第一個數前邊有多少數沒有給出來,所以這樣就可以一直求到第二個數,現在就只剩一個數,很簡單的,我在這裏就不提示了(實在想不出來可以看看我的flag數組)
#include<cstdio> #include<cstring> const int MAXN=1e5+1; int ans[MAXN],c[MAXN],num[MAXN],n; bool flag[MAXN]; int getsum(int x){//求前x和 int re=0; for(;x;x-=x&(-x)) re+=c[x]; return re; } void change(int x){//改變樹狀數組的值 for(;x<=n;x+=x&(-x)) c[x]++; return; } int main() { scanf("%d",&n); memset(flag,false,sizeof(flag));//設置標記找第一個人的序號 memset(c,0,sizeof(c)); for(int i=2;i<=n;i++) scanf("%d",&num[i]); int l,r,mid; for(int i=n;i>=2;i--){ l=1;r=n+1; while(l<r){//二分思想確定下標 mid=(l+r)>>1; if(mid>getsum(mid-1)+num[i]+1)//數>(後面小於次數的人數)+(前面小於次數對的人數,也就是num[i])+1 r=mid;//此數偏大 else l=mid+1;//次數偏小 } change(r-1);//找到數字,數字等於r-1 ans[i]=r-1; flag[r-1]=true; } for(int i=1;i<=n;i++){//爲第一個人找編號 if(!flag[i]){ ans[1]=i; break; } } for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0;//give me five }