[Gym102759C] Economic One-way Roads

题目

点这里看题目。

分析

玄学题目。直接搬运题解.jpg

解决这道题首先需要用到处理强连通图的一种特殊方法:耳分解

定理:一个有向图 (G=(V,E)) 为强连通,当且仅当它可以通过如下方法构造:

  • 维护一个图 (G'=(V',E'));初始时,(V'={v},E'=varnothing),其中 (vin V)

  • 循环地执行接下来两步,直到 (G'=G) 或无法执行:

    • 选取 (x,yin V')(x) 可以等于 (y)

    • 选取零个或多个互不相同的点 (u_1,u_2,dots,u_k),满足 (forall 1le ile k,u_i otin V'),同时还满足 ((x,u_1)in E,(u_k,y)in E,forall1le i<k,(u_i,u_{i+1})in E)

      将路径 (x ightarrow u_1 ightarrow u_2 ightarrow dots ightarrow u_k ightarrow y) 加入到 (G') 中。

      这里的路径 (x ightarrow u_1 ightarrow u_2 ightarrow dots ightarrow u_k ightarrow y) 就是“耳”。


容易使用归纳法证明,这样构造出来的图一定是强连通的。

但是充分性我还是不会证明

根据耳分解,我们不难想到根据这个过程进行 DP。设 (f_S) 表示当前 (V'=S),所需的最小花费。转移可以枚举一个“耳”,对于 (S) 总共有不超过 (2^{n-|S|}S^2) 种需要枚举检验的耳的方案。这样复杂度是 (O(3^nn^2)) 的。

但是转移会出现两个问题:其一,有可能有些边压根没有被计算到;其二,由于耳可以只有一条边,所以 (f_S) 会出现环形转移。

一种巧妙的解决方法是:对于原图上的某条边,它的两种定向方案的花费分别为 (a,b)。由于我们一定要对它定向,那么我们可以先钦定它取花费较小的方向,也就是 (min{a,b}) 对应的方向;这样再定向的花费就变成了 (a-min{a,b},b-min{a,b})

这样我们就得到了正确但超时的算法。但是注意到在这个算法中,很多种耳的方案都是不合法的。我们可以通过先选取 (x,y),再在 (E) 上遍历出一条合法的耳。事实上,我们可以直接将当前的遍历信息记录到状态中:

设新状态 (g_{S,u,v,0/1}) 表示当前 (V') 和一个未完成的耳的并集为 (S)、当前已经完成了 (x ightarrow u_1 ightarrow u_2 ightarrow dots ightarrow u) 的路径、耳的终点在 (v)、当前不可以/可以连接 ((u,v)) 的情况下,最小的花费。转移可以枚举 (u) 的下一个点 (w),转移到 (g_{Scup{w},w,v,1});如果可以连接 ((u,v)),也可以直接转移到 (f_S)。此时 (f) 也可以参与转移,同样是枚举新耳在 (S) 内的起点 (x) 和终点 (y);由于此时只有一条边的耳不需要考虑,我们可以直接枚举下一个点 (w,w otin S) 进行转移。注意,如果 (x=y),那么现在就不能连接 ((w,y))——我们不能将一条边定向两次。

这样我们得到了一个复杂度为 (O(2^nn^3)) 的算法,细节也比较少。

小结:

  1. 关注一下“耳分解”这种处理强连通图的方法;
  2. 如果多种方案有不同代价,从中必选一个,那么可以先选择最优的再来调整方案,记录一下这种方法;

代码

#include <cstdio>
#include <cstring>
#include <iostream>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int INF = 0x3f3f3f3f;
const int MAXN = 18, MAXS = 1 << 18;

template<typename _T>
void read( _T &x )/*{{{*/
{
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}/*}}}*/

template<typename _T>
void write( _T x )/*{{{*/
{
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}/*}}}*/

int dp[MAXS][MAXN][MAXN][2];
int f[MAXS];

int G[MAXN][MAXN];
int N;

void Upt( int &x, const int v ) { x = std :: min( x, v ); }

int main()
{
	read( N ); int ans = 0, lim;
	for( int i = 0 ; i < N ; i ++ )
		for( int j = 0 ; j < N ; j ++ )
			read( G[i][j] );
	for( int i = 0 ; i < N ; i ++ )
		for( int j = i + 1 ; j < N ; j ++ )
		{
			if( G[i][j] < 0 ) continue;
			int t = std :: min( G[i][j], G[j][i] );
			ans += t, G[i][j] -= t, G[j][i] -= t;
			lim += t + G[i][j] + G[j][i];
		}
	int all = ( 1 << N ) - 1;
	memset( f, 0x3f, sizeof f );
	memset( dp, 0x3f, sizeof dp );
	f[1] = 0;
	for( int S = 1 ; S <= all ; S ++ )
	{
		for( int u = 0 ; u < N ; u ++ )
			for( int v = 0 ; v < N ; v ++ )
				for( int b = 0 ; b <= 1 ; b ++ )
				{
					if( dp[S][u][v][b] > lim ) continue;
					for( int T = all ^ S ; T ; T &= T - 1 )
					{
						int w = __builtin_ctz( T );
						if( G[u][w] >= 0 )
							Upt( dp[S | ( 1 << w )][w][v][1], dp[S][u][v][b] + G[u][w] );
					}
					if( b && G[u][v] >= 0 )
						Upt( f[S], dp[S][u][v][b] + G[u][v] );
				}	
		for( int u = 0 ; u < N ; u ++ ) if( S >> u & 1 )
			for( int v = 0 ; v < N ; v ++ ) if( S >> v & 1 )
				for( int w = 0 ; w < N ; w ++ ) if( ! ( S >> w & 1 ) )
				{
					if( G[u][w] < 0 ) continue;
					Upt( dp[S | ( 1 << w )][w][v][u != v], f[S] + G[u][w] );
				}
	}
	if( f[all] > lim ) puts( "-1" );
	else write( ans + f[all] ), putchar( '
' );
	return 0;
}
原文地址:https://www.cnblogs.com/crashed/p/15143366.html