kmjp's blog

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

TopCoder SRM 634 Div2 Hard SpecialStrings

なかなか面白い問題。
http://community.topcoder.com/stat?c=problem_statement&pm=13233

問題

文字列が以下の条件を満たすときspecialであるという。

  • '0'と'1'で構成される。
  • Sを前半後半の空でない2つの部分文字列に分けたとき、分割位置に依らず前半の方が辞書順で前に来る。

specialな文字列Sが与えられる。
辞書順でSの次に来るspecialな文字列Tを答えよ。

解法

先に面倒なのでSをインクリメントしておく。
すると「Sの次に来るspecialな文字列T」ではなく「S以上で辞書順最小なspecial文字列T」を答える問題となる。

辞書順最小を目指すので、前から順に決めていく。
T=xxxxyyyyでxxxxまでの部分はspecialである、すなわちx < xxxyyyy、xx < xxyyyy、xxx < xyyyy、xxxx < yyyyを満たしているなら、T=xxxx1111は必ずspecialであることを利用する。

例えば上の数文字が確定してT=xxxx????とする。

出来れば辞書順最小にしたいので、T=xxxx0111と仮置きする。
この時、

  • 先頭部分のxxxx0は、同じ長さのSのprefixと等しいか、辞書順で後である。
  • T=xxxx0111はspecialである

の両方を満たすなら、Tの5文字目は0で確定し、T=xxxx0????として残りの桁を決める。
条件を満たさないなら、Tの5文字目は1を取るしかなく、T=xxxx0????として残りの桁を決める。

class SpecialStrings {
	public:
	string S,goal;
	string next(string s) {
		int i;
		for(i=s.size()-1;i>=0;i--) {
			s[i]++;
			if(s[i]<='1') break;
			s[i]='0';
		}
		return s;
	}
	bool issp(string s) {
		int i,L=s.size();
		for(i=1;i<L;i++) if(s.substr(0,i)>=s.substr(i)) return false;
		return true;
	}
	void dfs(string cur) {
		if(cur.size()==S.size()) {
			if(goal>cur && issp(cur)) goal=cur;
			return;
		}
		
		string n=cur+"01111111111111111111111111111111111111111111111111111111";
		n=n.substr(0,S.size());
		if(n.substr(0,cur.size()+1)>=S.substr(0,cur.size()+1)) {
			if(issp(n)) {
				dfs(cur+"0");
				return;
			}
		}
		dfs(cur+"1");
	}
	
	string findNext(string s) {
		if(s=="1") return "";
		S=next(s);
		goal="11111111111111111111111111111111111111111111111111111";
		dfs("");
		if(goal.size()>50) return "";
		return goal;
	}
}

まとめ

辞書順最小では「とりあえずこの桁に小さい文字を選べば、後ろは最悪のケースでもなんとかなるなら、その文字を選んでしまう」という手法はしばしば利用できるね。
B: 辞書式順序 - AtCoder Beginner Contest #007 | AtCoder
これとかね。