TopCoder SRM 541 Div1 Medium AkariDaisukiDiv1

問題

文字列A, B, Cが与えられたとき、
文字列xに対する操作fを、f(x) = A + x + B + x + Cとする。
(+は文字列の結合を表す)


fをn回適用する操作(f(f...(f(f(x)))...))をf^nと書く。
f^k(x)に、文字列Fが何回出現するかを求めよ。

制約条件

A, B, C, Fの長さは50以下
k≦1000万

方針

文字列の左端、右端が何か、端以外でFが何回出てくるかを持つ。


fを一回適用するたびに、
端以外での個数は2倍になり、
左端、中央、右端の部分で新たにFが出来る場合があるので、
その個数を足す。


その後左端、右端を更新する。
という風にして数えればいい。


愚直にやるとTLEなので、
左端、右端の文字列をメモ化して、同じ文字列が出てきたら、
後はループするのでまとめて処理してやると数百msで通る。

ソースコード

const int mod = (int)1e9 + 7;
int count(const string &a, const string &b){
	int res = 0;
	rep(i, (int)a.size() - (int)b.size() + 1){
		if(a.substr(i, b.size()) == b) res++;
	}
	return res;
}

class AkariDaisukiDiv1 {
	public:
	int countF(string Waai, string Akari, string Daisuki, string S, string F, int k) 
	{
		string s = Waai + S + Akari + S + Daisuki;
		
		k--;
		while(k > 0 && s.size() < F.size() + 50){
			s = Waai + s + Akari + s + Daisuki;
			k--;
		}
		
		int ans = count(s, F);
		if(k == 0) return ans;
		
		string suf = s.substr(s.size() - F.size() + 1);
		string pre = s.substr(0, F.size() - 1);
		
		map<pair<string, string>, int> memo;
		map<int, int> memo2;
		
		rep(it, k){
			if(memo.count(mp(pre, suf))){
				int prev = memo[mp(pre, suf)];
				int len = it - prev;
				while(it < k){
					ans = ans * 2 + memo2[(it - prev) % len + prev];
					ans %= mod;
					it++;
				}
				break;
			}
			memo[mp(pre, suf)] = it;
			
			string npre = Waai + pre;
			string nsuf = suf + Daisuki;
			
			int diff = count(nsuf, F) + count(npre, F) + count(suf + Akari + pre, F);
			memo2[it] = diff;
			
			ans = ans * 2 + diff;
			ans %= mod;
			
			suf = nsuf.substr(nsuf.size() - F.size() + 1);
			pre = npre.substr(0, F.size() - 1);
		}
		
		return ans;
	}
};