在上面這道例題中,就是典型的康託逆展開,不過由於n太大,因此它分別給你了遍歷到每一位數時,在剩下未便利的數中有幾個數比本身小,咱們仍是用樹狀數組來維護,可是這裏又加上了一個二分查找的方法,代碼就成形了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int k,n,ans[1000001];//記錄排列 4 int t[1000001];//樹狀數組 5 int lowbit(int x) 6 { 7 return x&-x; 8 } 9 void update(int x,int v)//區間修改 10 { 11 while(x<=n) 12 { 13 t[x]+=v; 14 x+=lowbit(x); 15 } 16 } 17 int sum(int x)//單點查詢 18 { 19 int p=0; 20 while(x>0) 21 { 22 p+=t[x]; 23 x-=lowbit(x); 24 } 25 return p; 26 } 27 int main() 28 { 29 cin>>k; 30 while(k--)//樣例個數 31 { 32 scanf("%d",&n); 33 for(int i=1;i<=n;i++) 34 { 35 update(i,1);//初始化 36 } 37 int val; 38 for(int i=1;i<=n;i++) 39 { 40 scanf("%d",&val);//一位一位的遍歷 41 int l=1,r=n; 42 while(l<r)//二分查找這個數 43 { 44 int mid=(l+r)/2; 45 int q=sum(mid)-1; 46 if(q<val)l=mid+1; 47 else r=mid; 48 } 49 update(r,-1);//並實錘這個數 50 ans[i]=r;//記錄 51 } 52 for(int i=1;i<n;i++) 53 { 54 printf("%d ",ans[i]);//輸出控制一下格式 55 } 56 printf("%d\n",ans[n]); 57 } 58 }
展開&逆展開運用
讓咱們來看看這一道(變態)題CF501D Misha and Permutations Summation
這道題正好是咱們上面講到的康託展開和逆展開的一個典型運用,題目大意就是給你兩個排列,讓你求出兩個排列名次的總和,並對這個排列的全部可能取模,最後輸出這個名次表明的排列。
首先它可能腦子有問題,對於從0開始的排列,每一位加一就好了,最後輸出減一便可。
首先是獲得名次的操做,剛開始確定想着直接算出排列名次,可是看看n的範圍(n≤200000)
顯然不行,咱們就退一步,先想想怎麼得出的康託展開值
拿213作例子,它的康託展開值等於1*2!+0*1!+0*0!
咱們再看看以前作逆康託展開的代碼,不就是根據每一個階乘前面的數字(1,0,0)獲得嗎(能夠用逆展開的代碼試一下),因此咱們就不用求出具體的排名了,只須要開一個數組(f[n])來記錄每個階乘的數量就能夠,而後從第一位開始,一位一位的進位,可是記住f[n]能夠不用管,由於它對n!取模就沒了.....最後就簡化成康託逆展開的模板了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n; 4 int a[2000001];//第一個排列 5 int b[2000001];//第二個排列 6 int f[2000001];//記錄康託展開值 7 int t[2000001];//樹狀數組 8 int lowbit(int x) 9 { 10 return x&-x; 11 } 12 void update(int x,int v)//區間修改 13 { 14 while(x<=n) 15 { 16 t[x]+=v; 17 x+=lowbit(x); 18 } 19 } 20 int sum(int x)//單點查詢 21 { 22 int ans=0; 23 while(x>0) 24 { 25 ans+=t[x]; 26 x-=lowbit(x); 27 } 28 return ans; 29 } 30 void init()//初始化樹狀數組 31 { 32 memset(t,0,sizeof(t)); 33 for(int i=1;i<=n;i++) 34 { 35 update(i,1); 36 } 37 } 38 int main() 39 { 40 scanf("%d",&n); 41 //記錄排列,每一位加一方便計算 42 for(int i=1;i<=n;i++) 43 { 44 scanf("%d",&a[i]); 45 a[i]++; 46 } 47 for(int i=1;i<=n;i++) 48 { 49 scanf("%d",&b[i]); 50 b[i]++; 51 } 52 /*分別記錄康託展開每一位的值,並把兩個排列的累計起來 , 53 由於最後一位的康託展開值必定就是0,因此不必*/ 54 init(); 55 for(int i=1;i<n;i++) 56 { 57 int ans=sum(a[i])-1; 58 f[n-i]+=ans; 59 update(a[i],-1); 60 } 61 init(); 62 for(int i=1;i<n;i++) 63 { 64 int ans=sum(b[i])-1; 65 f[n-i]+=ans; 66 update(b[i],-1); 67 } 68 69 for(int i=1;i<n;i++)//進位操做,就像3!*4=4!同樣,只操做到n-1 70 { 71 f[i+1]+=f[i]/(i+1); 72 f[i]=f[i]%(i+1); 73 } 74 //康託逆展開 75 init(); 76 for(int i=n-1;i>=1;i--)//從高到低一位一位的輸出 77 { 78 int l=1,r=n,mid; 79 while(l<r) 80 { 81 mid=(l+r)/2; 82 if(sum(mid)-1<f[i])l=mid+1; 83 else r=mid; 84 } 85 cout<<r-1<<" ";//由於以前加了一,因此後面減一便可 86 update(r,-1); 87 } 88 //輸出最後一位,由於只剩一個數的排名是一,其餘的都被搜到了,排名沒有了 89 for(int i=1;i<=n;i++) 90 { 91 if(t[i]) 92 { 93 cout<<i-1<<endl; 94 return 0; 95 } 96 } 97 }