kmjp's blog

競技プログラミング参加記です

yukicoder : No.295 hel__world

あとちょっとだったのに…。
http://yukicoder.me/problems/400

問題

文字列SのT数とは、Sから(元の前後順は入れ替えずに)|T|個の文字を抜き出して連結するとTを作れるような抜き出し位置の組み合わせ数とする。

文字列Sを構成する各アルファベットの登場回数がS[i]とする。
S[i]とTが与えられたとき、考えられるSのT数の最大値を求めよ。
ただし値が2^62以上となるなら"hel"と答えよ。

解法

まず、Tにおける各アルファベットの登場回数がS[i]未満の1個でもあるなら、早々に0を返してしまおう。

アルファベットごとに考える。
例えばT中の'a'に注目したとき、T = ___aaa___aaa___aaaa___のような形をしているとする。
T中連続した対象のアルファベットの数の数列Vを考える(上の例ならV=[3, 3, 4])。

ここでS['a']個のアルファベットを、|V|個に分ける。例えばS['a']=15のとき、W=[5, 4, 6]のように分けられる。
すると、SからTを抜き出すときの'a'の抜き出し方は \displaystyle \prod_{1 \le i \le |V|} {}_{W_i} C_{V_i}である。
ここまで来ると、上記積を最大化するようなWを求める問題に帰着できる。

まず初期状態としてW=Vとする。残りS['a']-sum(V)個の'a'をWのどこに振り分けるか考えることになる。
ここは、上記積が大きくなるようなところ振り分けるのがよさそうである。
 {}_{W_i} C_{V_i}でW[i]が1個増えて {}_{W_i+1} C_{V_i}になると、全体の積は \frac{W_i+1}{W_i+1-V_i}倍になる。
よってPriorityQueueで有理数 \frac{W_i+1}{W_i+1-V_i}の最大値を取っていくと良い。

ただ、この処理はS['a']-sum(V)回 PriorityQueueを辿るので、S['a']が大きいとTLEする。
 \displaystyle \prod_{1 \le i \le |V|} {}_{W_i} C_{V_i}はS['a']が大きいととても大きくなるので、2^62を早々に超えそうな場合はさっさと抜けてしまうのが良い。
この積はO(S[i]^sum(V))位の値になるので、S['a']が10^7以上、sum(V)が3以上なら2^62以上になるのは確実だし、sum(V)≦2なら簡単に組み合わせ計算で求められる。

ll S[30];
string T;
int L;
vector<int> V[26];
int num[26];

struct comp {
	bool operator()(const pair<pair<ll,ll>,pair<ll,ll>> a, const pair<pair<ll,ll>,pair<ll,ll>> b) const {
		return a.first.first*b.first.second < a.first.second*b.first.first;
	}
};

__int128 hoge(ll s,vector<int> p) {
	if(p.empty()) return 1;
	sort(ALL(p));
	
	__int128 ss=s;
	if(s>10000000) {
		if(p.size()==1) {
			if(p[0]==1) return s;
			if(p[0]==2) return ss*(ss-1)/2;
		}
		if(p.size()==2 && p[0]==1 && p[1]==1) return (ss/2)*(ss-ss/2);
		return 1LL<<62;
	}
	
	__int128 pat=1;
	priority_queue<pair<pair<ll,ll>,pair<ll,ll>>,vector<pair<pair<ll,ll>,pair<ll,ll>>>,comp> Q;
	FORR(r,p) s-=r, Q.push(make_pair(make_pair(r+1,1),make_pair(r,r)));
	
	while(s--) {
		auto r=Q.top();
		Q.pop();
		
		pat=pat * r.first.first / r.first.second;
		if(pat>=1LL<<62) return pat;
		
		r.second.first++;
		Q.push(make_pair(make_pair(r.second.first+1,r.second.first+1-r.second.second),r.second));
	}
	return pat;
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	FOR(i,26) cin>>S[i];
	cin>>T;
	L=T.size();
	
	V[T[0]-'a'].push_back(1);
	FOR(i,L-1) {
		if(T[i]==T[i+1]) V[T[i+1]-'a'].back()++;
		else V[T[i+1]-'a'].push_back(1);
	}
	FOR(i,L) num[T[i]-'a']++;
	
	__int128 ret=1;
	FOR(i,26) if(num[i]>S[i]) return _P("0\n");
	FOR(i,26) {
		__int128 pat=hoge(S[i],V[i]);
		if(pat>=1LL<<62) return _P("hel\n");
		ret*=pat;
		if(ret>=1LL<<62) return _P("hel\n");
	}
	cout<<(ll)ret<<endl;
}

まとめ

解法の1行目に書いたことをやってなくてWAしてた。