zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    备忘录
    中缀表达式转为后缀表达式
    未出现的最小正整数
    摩尔投票算法
    两个等长升序序列找中位数
    Morris二叉树遍历
    2020牛客寒假算法基础集训营5 街机争霸
    2020牛客寒假算法基础集训营5 牛牛战队的比赛地
    2020牛客寒假算法基础集训营2 求函数
    2020牛客寒假算法基础集训营2 建通道
  • 原文地址:https://www.cnblogs.com/crashed/p/15143366.html
Copyright © 2011-2022 走看看