• [SHOI2015]聚变反应炉


    题目

    点这里看题目。

    分析

    数据特殊,显然需要数据分治。

    max{c} ≤ 1

    此时 (c=0) 的点没有贡献,那么就相当于 (c) 全部相等。这样 (c) 最终的贡献与 (d) 无关,我们把 (c=1) 的点全部模拟点亮一遍即可。

    max{c} > 1

    不难想到做树形 DP 。我们可以想到这样的状态:

    (f(u,0/1,0/1)):第一个 (0/1) 表示 (u) 是否被激发,第二个表示 (u) 的父亲是否被激发,在这个情况下将 (u) 子树内的点全部激发的最小代价。

    然后你发现这样做复杂度不对无法处理儿子之间的转移顺序,或者说,我们无法有序确定哪些儿子在当前点之后被激发。其实可以,直接枚举全排列。

    但注意到,如果一个儿子 (v)(u) 之前激发,那么 (v) 就会给 (u)(C_v) 的贡献;如果 (v) 在之后,那么 (u) 就会给 (v)(C_u) 的贡献。既然一个儿子最多只有两种状态,我们就可以考虑 0/1 背包

    考虑状态 (f(u,0/1)):表示 (u) 的父亲是否激发,此情况下 (u) 子树内全部激发的最小代价。

    那么中途可以用背包进行转移, (g(i)) 表示考虑完一些儿子之后,当前点接受的能量为 (i) 的最小代价。转移显然:

    [g(i)=min{g'(i-C_v)+f(v,0),g'(i)+f(v,1)} ]

    最后 (f(u,0/1)) 都可以用 (g) 算出来。

    注意到 (c) 很小,于是复杂度就是 (O(cn^2))

    小结:

    1. 利用 0/1 背包处理只含两种情况的转移顺序的方法,具有借鉴意义。

    代码

    #include <cstdio>
    #include <cstring>
    
    const int MAXN = 1e5 + 5, MAXS = 1e4 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0; char s = getchar(); int f = 1;
    	while( s < '0' || '9' < s ) { 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' );
    }
    
    template<typename _T>
    _T MAX( const _T a, const _T b )
    {
    	return a > b ? a : b;
    }
    
    template<typename _T>
    _T MIN( const _T a, const _T b )
    {
    	return a < b ? a : b;
    }
    
    struct Edge
    {
    	int to, nxt;
    }Graph[MAXN << 1];
    
    int head[MAXN], D[MAXN], C[MAXN];
    int N, cnt;
    
    void AddEdge( const int from, const int to )
    {
    	Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
    	head[from] = cnt;
    }
    
    namespace AllOne
    {
    	bool Chk()
    	{
    		for( int i = 1 ; i <= N ; i ++ )
    			if( C[i] > 1 ) return false;
    		return true;
    	}
    	
    	void Solve()
    	{
    		int ans = 0;
    		for( int i = 1 ; i <= N ; i ++ )
    			if( C[i] == 1 )
    			{
    				ans += D[i], D[i] = 0;
    				for( int k = head[i] ; k ; k = Graph[k].nxt )
    					D[Graph[k].to] --;
    			}
    		for( int i = 1 ; i <= N ; i ++ ) ans += MAX( D[i], 0 );
    		write( ans ), putchar( '
    ' );
    	}
    }
    
    namespace Trivial
    {
    	int f[MAXN][2];
    	int su = 0;
    	
    	void DFS( const int u, const int fa )
    	{
    		int dp[MAXS]; memset( dp, 0x3f, sizeof dp );
    		for( int i = head[u], v ; i ; i = Graph[i].nxt )
    			if( ( v = Graph[i].to ) ^ fa )
    				DFS( v, u );
    		dp[0] = 0;
    		for( int i = head[u], v ; i ; i = Graph[i].nxt )
    			if( ( v = Graph[i].to ) ^ fa )
    				for( int j = su ; ~ j ; j -- )
    					if( j >= C[v] ) dp[j] = MIN( dp[j] + f[v][1], dp[j - C[v]] + f[v][0] );
    					else dp[j] += f[v][1];
    		for( int i = 0 ; i <= su ; i ++ )
    			f[u][0] = MIN( f[u][0], dp[i] + MAX( 0, D[u] - i ) ),
    			f[u][1] = MIN( f[u][1], dp[i] + MAX( 0, D[u] - i - C[fa] ) );
    	}
    	
    	void Solve()
    	{
    		memset( f, 0x3f, sizeof f );
    		su = N * 5, DFS( 1, 0 );
    		write( f[1][0] ), putchar( '
    ' );
    	}
    }
    
    int main()
    {
    	read( N );
    	for( int i = 1 ; i <= N ; i ++ ) read( D[i] );
    	for( int i = 1 ; i <= N ; i ++ ) read( C[i] );
    	for( int i = 1, a, b ; i < N ; i ++ )
    		read( a ), read( b ), AddEdge( a, b ), AddEdge( b, a );
    	if( AllOne :: Chk() ) return AllOne :: Solve(), 0;
    	Trivial :: Solve();
    	return 0;
    }
    
  • 相关阅读:
    IntelliJ IDEA 修改内存大小,使得idea运行更流畅。(转发)
    QueryRunner使用总结
    C#中static修饰符的作用
    Design Support库中的控件
    关于tomcat部署项目的问题
    让后台服务一直运行
    活动和服务的通信
    服务的用法
    数组右移问题
    素数对猜想
  • 原文地址:https://www.cnblogs.com/crashed/p/14008330.html
Copyright © 2020-2023  润新知