Kth Sentence (AOJ2341)

500点

問題:
lowercaseからなる文字列(長さ200まで)がN(<=100)個与えられる.これを並べて(それぞれ複数回使っても良いし,一回も使わなくても良い)できる長さM(<=2000)の文字列のうち,辞書順K(<=1e18)個めのものを求めよ.


解答:
aから始まるのは何通り?
bから・・・みたいなのをやって,どれから始まるか特定する.その後もcaから,cbから・・・みたいなのをやってく.
例えばacbまでは確定してて,acba,acbb...と調べるためには,acbのbがどこ由来のやつ(の右を考慮してない並べ方)が何通りあるか?というのを持っとけば良い.例えばac bdみたいに並べられてるんだったら次はdしかない, acb とか a cbみたいに並べられてるんだったら,次は好きな文字列が置ける."どこ由来"は「文字列iの長さjのところ」みたいなのを持てばいい.それらが分かってたら,遷移はさっき言ったとおりで,実際に何通りか数えるには,way[i]=長さiの文字列を作る方法 みたいなのを前計算しといて,way[後何文字埋めればいいか]をそれぞれに対しかければいい.
オーバーフロー:
足し算,かけざんがK以上にならないようにする(ab>c⇔a>c/b ただし切り捨て割り算 a,b,c>0)

#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<(int)(n);i++)
#define rep1(i,n) for(int i=1;i<=(int)(n);i++)
#define all(c) c.begin(),c.end()
#define pb push_back
#define fs first
#define sc second
#define show(x) cout << #x << " = " << x << endl
#define chmin(x,y) x=min(x,y)
#define chmax(x,y) x=max(x,y)
using namespace std;
typedef long long ll;
int N,L;
ll K;
void add(ll &x,ll y){
	x=min(x+y,K);
}
ll pro(ll x,ll y){
	if(x==0||y==0) return 0;
	if(x>K/y) return K;
	return x*y;
}
string s[100];
int n[100];
ll way_[2400];
ll *way=way_+300;
ll dp[100][200];
int main(){
	cin>>N>>L>>K;
	rep(i,N) cin>>s[i];
	rep(i,N) n[i]=s[i].size();
	way[0]=1;
	rep(i,L){
		rep(j,N) if(i+n[j]<=L) add(way[i+n[j]],way[i]);
	}
	string ans;
	dp[0][n[0]-1]=1;
	rep(i,L){
		ll nx[26]={};
		rep(j,N){
			rep(k,n[j]){
				if(k==n[j]-1){
					rep(l,N) add(nx[s[l][0]-'a'],pro(dp[j][k],way[L-i-n[l]]));
				}else{
					add(nx[s[j][k+1]-'a'],pro(dp[j][k],way[L-i-n[j]+k+1]));
				}
			}
		}
		ll sum=0;
		int A=-1;
		rep(j,26){
			ll tmp=sum;
			sum+=nx[j];
			if(sum>=K){
				K-=tmp;
				A=j;
				break;
			}
		}
		if(A<0){
			assert(i==0);
			puts("-");
			return 0;
		}
		ans+='a'+A;
		ll ndp[100][200]={};
		rep(j,N){
			rep(k,n[j]){
				if(k==n[j]-1){
					rep(l,N) if(s[l][0]-'a'==A) add(ndp[l][0],dp[j][k]);
				}else{
					if(s[j][k+1]-'a'==A) add(ndp[j][k+1],dp[j][k]);
				}
			}
		}
		rep(j,N) rep(k,n[j]) dp[j][k]=ndp[j][k];
	}
	cout<<ans<<endl;
}