Codeforces 121 C Lucky Permutation

問題

lucky numberとは10進数で表したときに、全ての桁の数字が4または7である数を言う。

1からnまでの整数の順列のうち、k番目のものについて、
それぞれの数字をa[1],a[2],...,a[n]とおく。

1≦i≦nで、iがlucky numberかつa[i]がlucky numberであるようなiの個数を求めよ。

制約条件

n,k≦10^9

方針

k番目の順列は、kが10^9以下なので、
順番が入れ替わっているのはせいぜい後ろの20項くらいである。


したがって、後ろの20項くらいだけ、具体的に順列を求めてやって、
iとa[i]がともにlucky numberになっている個数を数えればよい。


それに、残りのn-20項でlucky numberになっている数
(これは、1,2,3,...,n-20と並んでいるはずなので、単にlucky numberの数を数えるだけでよい)を足してやればよい。

ソースコード

const int LIM=(int)1e9;
int n,k;
ll fact[20];

vector<ll> lucky;
void gen(ll c){
  lucky.pb(c);
  if(c*10+4<LIM)gen(c*10+4);
  if(c*10+7<LIM)gen(c*10+7);
}
bool islucky(int x){
  for(;x;x/=10)if(x%10!=4&&x%10!=7)return 0;
  return 1;
}
void run(){
  cin>>n>>k;
  fact[0]=1;
  rep(i,19)fact[i+1]=fact[i]*(i+1);
  
  int l=0;
  for(;fact[l]<k;l++);
  if(l>n){
    cout<<-1<<endl;
    return;
  }
  
  lucky.clear(); gen(0); 
  sort(all(lucky));
  
  bool use[20]={};
  int ans[20];
  rep(i,l){
    int ix=0;
    while(fact[l-i-1]<k)ix++, k-=fact[l-i-1];
    rep(j,l)if(!use[j]){
      if(ix--==0){
        use[j]=1;
        ans[i]=n-l+j+1;
        break;
      }
    }
  }
  
  int cnt=upper_bound(all(lucky),n-l)-lucky.begin();
  cnt--;
  
  rep(i,l)if(islucky(n-l+i+1)&&islucky(ans[i]))cnt++;
  cout<<cnt<<endl; 
}