BZOJ3301 P2524 UVA11525 算法解释康托展开
这三个题的代码分别对应第二个第一个第三个
在刘汝佳蓝书上我遇到了这个康托展开题。
当时去了解了一下,发现很有意思
百度上的康托展开定义
原理介绍
康托展开运算
其中,
为整数,并且
。
的意义为在ai之后出现的数有几个比他小
康托展开的逆运算
既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。
如n=5,x=96时:
首先用96-1得到95,说明x之前有95个排列.(将此数本身减去1)用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.用5去除2!得到2余1,类似地,这一位是3.用1去除1!得到1余0,这一位是2.最后一位只能是1.所以这个数是45321。
此定理的证明十分简易,就是用组合原理
我们能明白第k位上的数码若为x则有(n-k-1)!(x-1)种比他小的排列(字典序小)
就可以证了
显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。
我们可以发现正着求的话,阶乘on预处理,那么关键就在于ai怎么求。
我们观察到暴力是on2
如果把数变为布尔数组
转化问题为一般二位偏序
用树状数组求前面0的个数
不就是ai了么
所以就可以nlogn求解
发下暴力(洛谷2524)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 #define mod 19260817 6 int n,m,a,b,c,ans=0,list[1000],visit[1000]; 7 void makelist(){ 8 list[1]=1; 9 for(int i=2;i<=1000;i++) 10 list[i]=(list[i-1]%mod)*i%mod; 11 } 12 string s; 13 int main(){ 14 cin>>n; 15 memset(visit,0,sizeof(visit)); 16 makelist(); 17 cin>>s; 18 for(int i=0;i<s.length();i++){ 19 a=s[i]-'0';visit[a]=1; 20 m=0; 21 for(int j=1;j<a;j++){ 22 if(!visit[j])m++; 23 } 24 ans+=list[n-1-i]*m; 25 } 26 cout<<ans+1; 27 return 0; 28 }
轮到正解了(bzoj3301)
#include<iostream> #include<cstring> #include<cstdio> using namespace std; #define mod 19260817 int n,m,a,b,c,ans=0,list[100010],input[100010],tree[100010]; int lowbit(int x){return x&(-x);} int query(int x){int ans=0;for(int i=x;i>0;i-=lowbit(i))ans+=tree[i];return ans;} void add(int p,int x){for(int i=p;i<=n;i+=lowbit(i))tree[i]+=x;} void makelist(){ list[1]=1; for(int i=2;i<=1000;i++) list[i]=(list[i-1]%mod)*i%mod; } int main(){ cin>>n; makelist(); for(int i=1;i<=n;i++) cin>>input[i]; for(int i=1;i<=n;i++){ a=input[i];add(a,1); m=a-query(a); ans+=list[n-i]*m; } cout<<ans+1; return 0; }
那逆运算呢
由于我们知道ai<n
所以我们观察到(n-i)!*ai<n!
可以推出x/(n-i)!=ai;(下取整)
问题转化为二位偏序,前缀第k大
就可以用树状数组解决(也可以用主席树)
代码参考的candy博主
uva1125
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=5e5+5,INF=1e6+5; inline int read(){ char c=getchar();int x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } int n,x,k; int c[N]; inline int lowbit(int x){return x&-x;} inline void add(int p,int v){ for(;p<=n;p+=lowbit(p)) c[p]+=v; } inline int sum(int p){ int res=0; for(;p>0;p-=lowbit(p)) res+=c[p]; return res; } inline int kth(int k){ int x=0,cnt=0; for(int i=16;i>=0;i--){ x+=(1<<i); if(x>=n||cnt+c[x]>=k) x-=(1<<i); else cnt+=c[x]; } return x+1; } int main(){ int T=read(); while(T--){ n=read(); memset(c,0,sizeof(c)); for(int i=1;i<=n;i++) add(i,1); for(int i=1;i<=n;i++){ k=read()+1; x=kth(k); cout<<x<<" "; add(x,-1); } } }