SRM 406 DIV1 Hard ShortPaths

問題 Editorial

問題

無向重み付きグラフで以下の条件を満たすものgraphが与えられる:

  • それぞれの頂点に対して、たかだか1つの単純閉路に含まれる
  • それぞれの頂点v,uに対して、vからuへのsimple pathはたかだか1つ

頂点start,finishと正整数kが与えられる。startからfinishへのpathのうち、k番目に短いものの長さを求めよ。ただしk番目が存在しない場合は-1を返せ。

  • 2 ≦ |graph| ≦ 50
  • graphは問題文の条件を満たす
  • 1 ≦ それぞれの重み ≦ 9
  • 1 ≦ k ≦ 10^12
  • start ≠ finish

解答

まずstartからfinishへの最短路を求める。この時点でパスが存在しない場合-1を返す。
グラフの制約を考えると、最短路上の区間と閉路がくっついているような形になる。くっついている閉路は頂点に対してたかだか1つなので、くっついている区間がかぶることはない。
最短路の長さをpathlenとし、閉路のそれぞれの長さをcyclesとすると、startからfinishへのpathの長さは以下のように表せる:"pathlen + Σ (a_i cycle_i)"。また、このときの[a_i]によってのみpathが異なるので、[a_i]を数えればよいことがわかる。
答えを求めるために2分探索をする。このとき"Σ(a_i cycle_i)≦mid-pathlen"となるような[a_i]の数を数えたい。これは明らかにナップサック問題であるので、DPを使うことにする。しかし、最大の答えを見積もるとそれなりに大きく、単純にはできないことがわかる。ここで重要な観察:最大の答えは|cycles|が大きいほど小さい。
実際には、|cycles|が3以上であれば適切に普通のDPでいける。すなわち|cycles|≦2の時に場合分けをして計算する。

cycles が0の場合は"k==1 ? pathlen : -1"で、 cycles が1の場合は"pathlen + (k-1) * cycles[0]"となることは明らかである。
cycles が2の場合にはO(√k)でできる。まず、cycles[0]とcycles[1]の大きい方をx,小さい方をyとする。チェックする答えをmとする。すると組み合わせの数は"��_{k=0}^{m/x} (floor( (m-k*x)/y)+1)"となる。つまりxを何個使うかを全探索して、それに対して残りを最大何個使えるかを計算する。+1は0個の場合のため。(m/x)*(m/y)/2がkを超えるなら明らかに大きすぎるので計算を省ける。するとO(√k)となる。ちなみにこれをΘ(log k)で計算する方法も存在する("SRM 410 DIV1 Hard WifiPlanet"参照)。
cycles が3以上の場合は答えはたかだか3000000程度。2分探索のたびにDPしても間に合う。

この上限は以下のようにわかる:サイクルのサイズを均等に分けた時に答えが最大になるが、50/3≦17で重み9としても153。組み合わせは最大C(x+1,|cycles|)程度であってC(x,3)≧10^12となるようなxは18200程度で、それに153をかけても2784600程度だから。なお、C(x,y)で組み合わせの数がわかることはa_iをi-1番目の選択とi番目の選択の間の区間として考えればわかりやすい。

コメント

逆指数O(x^(1/y))の上限・アルゴリズム("逆指数"は造語)。

コード

#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i))
#define rer(i,l,u) for(int (i)=(int)(l);(i)<=(int)(u);++(i))
#define each(it,o) for(__typeof((o).begin()) it = (o).begin(); it != (o).end(); ++ it)
#define pb(x) push_back(x)
#define mset(m,v) memset(m,v,sizeof(m))
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3fLL
using namespace std;
typedef vector<int> vi;  
typedef long long ll;   

ll dp[3000010];
struct ShortPaths {
	ll counting(vi ws, ll x) {
		ll r = 0;
		if(x < 0) r = 0;
		else if(ws.size() == 2) {
			if((ll)(x/ws[0])*(x/ws[1])/2+1 > 1e12) return INFL;
			if(ws[0]<ws[1])swap(ws[0],ws[1]);
			int imax = x / ws[0];
			for(int i = 0; i <= imax; i ++)
				r += (x - (ll)ws[0] * i) / ws[1] + 1;
		}else {
			if(sizeof(dp)/sizeof(dp[0]) <= x) return INFL;
			memset(dp, 0, sizeof(dp[0])*(x+1));
			dp[0] = 1;
			rep(i, ws.size()) {
				int w = ws[i];
				rer(j, 0, x-w)
					if((dp[j+w] += dp[j]) > 1e12) return INFL;
			}
			rer(i, 0, x) r = min(r + dp[i], (ll)1e14);
		}
		return r;
	}
	long long getPath(vector <string> graph, long long K, int s, int t) {
		vi tmp;
		tmp.pb(9*17), tmp.pb(9*17), tmp.pb(9*16);
		static int wf[51][51];
		mset(wf, INF);
		int n = graph.size();
		rep(i, n) rep(j, n) if(graph[i][j] != '0') wf[i][j] = graph[i][j] - '0';
		rep(k, n) rep(i, n) rep(j, n) wf[i][j] = min(wf[i][j], wf[i][k] + wf[k][j]);
		if(wf[s][t] == INF) return -1;
		ll pathlen = 0;
		vi path;
		{	int i = s;
			while(1) {
				path.pb(i);
				if(i == t) break;
				for(int j = n-1; j >= 0; j --) if(graph[i][j] != '0' && (graph[i][j]-'0') + (j == t ? 0 : wf[j][t]) == wf[i][t]) {
					pathlen += graph[i][j]-'0';
					i = j;
					break;
				}
			}
		}
		cout << "pathlen: " << pathlen << endl;
		vi cycles;
		int m = path.size();
		rep(i, m) {
			int j = i;
			while(j < path.size() && wf[path[j]][path[i]] != INF) j ++;
			if(j == i) ;
			else {
				cycles.pb(wf[path[i]][path[i]]);
				i = j-1;
			}
		}
		cout << "cycles: "; each(i, cycles) cout << *i << ", "; cout << endl;
		if(cycles.empty()) 
			return K == 1 ? pathlen : -1;
		if(cycles.size() == 1)
			return pathlen + (cycles[0] * (K-1));
		ll l = -1, u = cycles.size() == 2 ? 500000000 : 3000000;
		while(u-l>1) {
			ll mid = (l+u) / 2;
			if(counting(cycles, mid) >= K) u = mid;
			else l = mid;
		}
		return pathlen + u;
	}
};