TopCoder SRM 608 Div1 Medium BigO

問題

n頂点からなる有向グラフが与えられる。
整数Lに対して、このグラフ上の長さLのウォークの個数がO(L^k)で抑えられるような最小のkを求めよ。


そのようなLが存在しないとき、は-1を返せ。

制約条件

n≦50
長さlのウォークとは、グラフの頂点の列v1, v2, ..., vlで、viとvi+1の間に辺があるようなものをいう。同じ頂点および辺を何度使ってもよい。
Lについての関数がO(L^k)であるとは任意のLに対してその関数が定数Cを使って
f(L) ≦ C * L^kと抑えられることを言う。

方針

-1にならないグラフというのは、相当強い制約がかかりそうというのがサンプルを見るとわかる。
よく考えてみると、頂点iから出発して、iに戻ってくる方法が2通り以上あると、ウォークの個数はO(L^k)で抑えられないことがわかる。
なので、まずは自分に2通り以上の方法で戻れないかをみてやればよい。


戻れない場合、ループがDAGのようにつながったグラフになる。
ループを一個下る度に、場合の数のオーダーが一次増える(Lのうち何歩目で下のループに移るか選べるから)


違うループに行く方法が2通り以上あっても定数倍なので、オーダーには関係ない。


以上のことから、強連結成分分解したグラフを考えて、一番長いチェーンの長さが答え。
実装はなんか適当に色々手を抜いている。

ソースコード

int n, way[100][50][50]; //i歩でjからkへ行く場合の数
bool can[50][50]; //iからjへ到達可能か
int dp[50];

int rec(int c){
	int &res = dp[c];
	if(res >= 0) return res;
	res = 0;
	//自分より下のループの最大値 + 1が答え
	rep(i, n) if(can[c][i] && !can[i][c] && can[i][i]) res = max(res, rec(i) + 1);
	return res;
}
class BigO {
	public:
	int minK(vector <string> graph) {
		memset(way, 0, sizeof(way));
		memset(dp, -1, sizeof(dp));
		
		n = graph.size();
		rep(i, n) rep(j, n) can[i][j] = way[1][i][j] = graph[i][j] == 'Y';
		rep(i, n) way[0][i][i] = 1;
		
		for(int it = 1; it < 99; it++) rep(k, n) rep(i, n) rep(j, n){
			int &w = way[it + 1][i][j];
			w += way[it][i][k] * way[1][k][j];
			w = min(w, 2); //1より大きいのは全部同じなので2で打ち切り
		}
		rep(i, n){
			int t = -1;
			for(int j = 1; j < 100; j++) if(way[j][i][i]){
				t = j;
				break;
			}
			if(t < 0) continue;
			for(int j = 1; j < 100; j++) if(way[j][i][i] != (j % t == 0 ? 1 : 0)) return -1;
		}
		
		rep(k, n) rep(i, n) rep(j, n) can[i][j] |= can[i][k] && can[k][j];
		int ans = 0;
		rep(i, n) if(can[i][i]) ans = max(ans, rec(i));
		
		return ans;
	}
};