[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;
}
原文地址:https://www.cnblogs.com/crashed/p/14008330.html