[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)\) 。
小结:
- 利用 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( '\n' );
}
}
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( '\n' );
}
}
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;
}
标签:return,int,void,激发,SHOI2015,聚变反应,dp,const 来源: https://www.cnblogs.com/crashed/p/14008330.html