題目大意:
ZYB's Premutation
ZYBZYB有一個排列PPP,但他只記得PPP中每個前綴區間的逆序對數,現在他要求你還原這個排列.
(i,j)(i<j)(i,j)(i < j)被稱爲一對逆序對當且僅當Ai>AjA_i>A_j
第一行一個整數TTT表示數據組數。
接下來每組數據:
第一行一個正整數NN,描述排列的長度.
第二行NN個正整數AiA_i,描述前綴區間[1,i][1,i]的逆序對數.
數據保證合法.
1≤T≤5,1≤N≤500001 \leq N \leq 50000
TT行每行NN個整數表示答案的排列.
1
3
0 1 2
3 1 2
這題是個cf的原題,要利用結點的數據,來找區間的標記。考的很靈活。
解題思路:
利用線段樹求逆序數,還原整個排列。首先設fif_i是第ii個前綴的逆序對數,pip_i是第ii個位置上的數,
則fi−fi−1是ii前面比pip_i大的數的個數.我們考慮倒着做,當我們處理完iii後面的數,第iii個數就是剩下的數中
第fi−fi−1+1f_i-f_{i-1}+1大的數。再利用線段樹。
首先線段樹每個結點表示的是區間長度。每次查詢線段樹,先訪問右區間,因爲右區間裏表纔是表示倒着數的大的數,若右區間的此時的長度不夠fi−fi−1+1,
說明此時這個數不在左邊的區間,則去查詢左邊區間。但要右邊減去區間的長度。因爲此時找的是左邊的區間第幾大的數,直到找到葉子結點。記錄它的左端點,就是當前
第的位置應該放的數。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=50000+1000;
int a[maxn],ans[maxn],t,n,p;
int sum[maxn<<4];
void build(int o,int L,int R)
{
int M=L+(R-L)/2;
if(L==R) sum[o]=1;
else
{
build(o*2,L,M);
build(o*2+1,M+1,R);
sum[o]=sum[o*2]+sum[o*2+1];
}
}
int query(int o,int L,int R,int p)
{
int M=L+(R-L)/2;
if(L==R)
{
sum[o]=0;
return L;
}
int ret;
if(sum[o*2+1]>=p)
ret=query(o*2+1,M+1,R,p);
else
ret=query(o*2,L,M,p-sum[o*2+1]);//右區間要剪掉此時左邊的值
sum[o]=sum[o*2]+sum[o*2+1];//更新當前結點
return ret;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
build(1,1,n);
a[0]=0;
for(int i=n;i>=1;i--)
{
int p=a[i]-a[i-1];
ans[i]=query(1,1,n,p+1);//加1注意
}
for(int i=1;i<=n;i++)
printf("%d%c",ans[i],i==n?'\n':' ');
}
return 0;
}