康托展开
- 求1 ~ n的某个排列在1 ~ n的全排列中的排名。
- 用b数组记录每个数在它之前没有出现的比它小的数的个数,第i个数的结果存放至b[n-i+1]。
- 排名ans=b[1] * 0!+b[2] * 1!+···+b[n] * (n-1)!+1,其中+1之前求得的比给定排列小的排列数,+1之后就是其排名。
- 证明: b[i]*(i-1)!表示的是前i-1位与给定排列相同的情况下比给定排列小的排列个数。没了。
- code:线段树优化,nlogn。
update(1,1,n,a[1],a[1],1);
b[n]=a[1]-1;
for(int i=2;i<=n;i++)
{
int tmp=query(1,1,n,1,a[i]-1);
b[n-i+1]=a[i]-1-tmp;
update(1,1,n,a[i],a[i],1);
}
jc[0]=jc[1]=1;
for(int i=1;i<=n;i++)
jc[i]=i*jc[i-1]%mod;
for(int i=1;i<=n;i++)
ans=(ans+jc[i-1]*b[i]%mod)%mod;
ans++;
逆康托展开
- 给定排名k和n,求1~N排名第k的排列。
- 对k减1,将前面的式子逆过来求出每一位即可。
for(int i=n;i>=1;i--)
{
int tmp=nw/jc[i-1],x=0,ac=0;
nw%=jc[i-1];
for(int j=1;j<=k;j++)
{
if(!val[j])
{
if(x==tmp)
{
val[j]=1;
ac=j;
break;
}
x++;
}
}
cout<<ac<<endl;
}
CF121C Lucky Permutation
- 逆康托展开的应用,n很大时k只会影响后几位,前面是从1开始顺序排列的,分两部分处理即可。
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int maxn=1e6+5;
long long n,k,jc[25],pos,ans;
bool val[25];
int charge(int x)
{
while(x)
{
if(x%10==4||x%10==7)
x/=10;
else
return 0;
}
return 1;
}
void solve(int num)
{
for(int j=1;j<=num;j++)
for(int i=0;i<=(1<<j)-1;i++)
{
int tmp=0,bas=1,nw=i;
for(int l=1;l<=j;l++)
{
tmp+=nw%2?bas*7:bas*4;
bas*=10;
nw=(nw>>1);
}
if(tmp>num) return ;
ans+=charge(tmp);
}
}
int main()
{
cin>>n>>k;
jc[0]=jc[1]=1;
k--;
for(int i=1;i<=20;i++)
jc[i]=i*jc[i-1];
for(int i=1;i<=20;i++)
{
if(jc[i]>k)
{
pos=i;
break;
}
}
if(jc[0]>k) pos=0;
pos--;
if(n<=20&&k+1>jc[n])
{
cout<<-1;
return 0;
}
int nw=k;
for(int i=pos;i>=1;i--)
{
int tmp=nw/jc[i],x=0,ac=0;
nw%=jc[i];
for(int j=1;j<=k;j++)
{
if(!val[j])
{
if(x==tmp)
{
val[j]=1;
ac=j;
break;
}
x++;
}
}
ac+=n-pos-1;
if(charge(n-i))
ans+=charge(ac);
}
for(int i=1;i<=k;i++)
if(!val[i])
{
if(charge(n))
ans+=charge(i+n-pos-1);
break;
}
solve(n-pos-1);
cout<<ans;
return 0;
}