torus711 のアレ

主に競技プログラミングの問題について書きます.PC 以外だと数式が表示されないかもしれないです

Codeforces #270, D : Design Tutorial: Inverse the Problem

問題概要

 n \times n ( n \leq 2000 ) の行列 d が与えられる。
 n 頂点からなる木であって、頂点 i, j 間の距離が d_{ij} となるものは存在するか?
 なお構成される木は、無向・重み付きであって、辺重みは全て正である。

解法

 後の処理を簡単にするために、すぐに分かる条件は先に弾きます。無向グラフなので、dd_{ii} = 0 かつ 0 < d_{ij} ( i \neq j ) かつ d_{ij} = d_{ji} を満たさなければ木は存在しません。この条件を満たすという条件下で、木を作れるか否か考えていきます。
 d を隣接行列を見做すと n 頂点の完全グラフを得られます。このグラフを以降 G と書きます。さて、G 上の最も短い辺で結ばれた二点は、目的の木に於いて直接辺で結ばれていなければなりません。これは、u, v が直接結ばれていないとすると、中継する点 w が存在して、d_{uw} + d_{wv} = d_{uv} となりますが、これは G に於いて u, v を結ぶ辺が最短であるという条件に矛盾するということから示されます。残った辺についても同様に考えていくと、Kruskal 法っぽい何かが見えてきて、最小全域木( MST )と関連がありそう、という気分になります。
 実際、構成が可能ならば、構成される木は G の MST になっていなければなりません。先程と同じような議論ですが、木が MST でないとすると、どこかの辺を付け替えて MST を得ることができるはずです。新しく張られる辺が G に於いて二点 u, v を結んでいるとすると、その重みは d_{uv} 未満でなければ前提に矛盾しますが、これは G の定義に矛盾します。従って、そのような辺は存在できず、仮定は誤りで木は MST になっているはずと分かります。
 ここまでの考察で、G の MST を一つ求めて、MST 上の距離を表す行列が d と一致するかを調べることで問題を解くことができると分かりました。全点間最短距離が必要になっていますが、これはは Prim 法と同時に計算することで全体を O( N^2 ) で処理できます。構成途中の MST に辺 ( a, b ) を加えたとき(点 b が新たに MST に加わる点であると一般性を損なわず仮定する)、各頂点 v に関して、d_{vb}d_{bv}d_{va} + d_{ab} で更新することができます。O( N ) かかる更新処理が O( N ) 回発生するので、更新処理のオーダーは O( N^2 ) になります。完全グラフの MST なので Prim 法は O( N^2 ) で実装した方がオーダーが軽くなり、全体でも O( N^2 ) となります。

コード

using VI = vector<int>; using VVI = vector<VI>;
template < typename T = int > using VT = vector<T>;
template < typename T = int > using LIM = numeric_limits<T>;

template < typename T > inline void input_n( VT< T > &out, const int N ) { out.resize( N ); copy_n( istream_iterator< T >( cin ), N, out.begin() ); };

#define REP( i, m, n ) for ( int i = (int)( m ); i < (int)( n ); ++i )
#define FOR( e, c ) for ( auto &e : c )
#define ALL( c ) (c).begin(), (c).end()

constexpr int INF = LIM<>::max() / 2;

int main()
{
	cin.tie( 0 );
	ios::sync_with_stdio( false );

	int n;
	cin >> n;

	VVI G( n );
	FOR( row, G )
	{
		input_n( row, n );
	}

	REP( i, 0, n )
	{
		if ( G[i][i] )
		{
			cout << "NO" << endl;
			return 0;
		}

		REP( j, 0, i )
		{
			if ( G[i][j] <= 0 || G[i][j] != G[j][i] )
			{
				cout << "NO" << endl;
				return 0;
			}
		}
	}

	VI costs = G[0], prev( n );
	VVI distance( n, VI( n, INF ) );			
	VT< bool > used( n );

	while ( true )
	{
		int u = -1, p = -1;
		REP( i, 0, n )
		{
			if ( !used[i] && ( u == -1 || costs[i] < costs[u] ) )
			{
				u = i;
				p = prev[u];
			}
		}

		if ( u == -1 )
		{
			break;
		}

		used[u] = true;
		distance[u][u] = 0;

		REP( v, 0, n )
		{
			distance[v][u] = distance[u][v] = min( distance[u][v],  distance[v][p] + G[p][u] );

			if ( G[u][v] < costs[v] )
			{
				costs[v] = G[u][v];
				prev[v] = u;
			}
		}
	}

	cout << ( distance == G ? "YES" : "NO" ) << endl;

	return 0;
}