树形dp-道路[HNOI2018]

题面真的好长(.jpg)

所以考场上某初二大佬打了85分的题我(TM)一分都没拿(QAQ)

(算了,我在讲什么?)

首先是把罪恶的题面化简:

给一棵树,1节点为根,叶子节点均为农村,城市都是非叶节点,每个城市都有两条边,一条为铁路,一条为公路,现在你可以对每个非叶节点 选择其中一条 进行番修。

番修后假设一个叶子节点到根要经过(x)条未被番修的道路,(y)条未被番修的公路,那么其对答案产生的贡献为:((c_i * (a_i+x) * (b_i+y)))

要求最小贡献。

这道题出在省选上,记得当时看的时候,以为它和毒瘤((Day1T3))一样毒瘤,而且题面太长了,就没写了.......

然而实际上个人觉得没有机房那些大佬说的那么水啊 QAQ

观察数据,我们发现其实可以记录每个点到根节点

首先考虑记录状态:(dp_{x,i,j})表示以(x)根的子树,上面有(i)条公路未被番修,(j)条铁路未被番修的答案。

如果(dp)到了最底下的叶子节点,我们可以(O(K^2))的去算答案。

当然,要记录每个点顶上有多少条道路和有多少条公路

对于非叶节点,转移也并不难想:


for( int i = 0; i <= dep1[x]; ++ i )
for( int j = 0; j <= dep2[x]; ++ j ) 
dp[x][i][j] = dp[ls(x)][i][j] + dp[rs(x)][i][j + 1], 
//这里表示修左儿子的公路,那么右儿子顶上就有一条铁路未被番修。

dp[x][i][j] = min( dp[x][i][j], dp[ls(x)][i + 1][j] + dp[rs(x)][i][j] );

//此处类似

然而这样会 (MLE)

我们可以发现,某次(dp)(dp)到某一层后,其在回溯去更新的点,只有它的父亲,换而言只:某个点的(dp)值只有在更新其父亲的时候才生效,然后其(dp)不会影响其他点

如果我们将原来的过程写成(dfs),我们可以发现,某次(dp)的过程中,对于某一种/层深度,我们只会用到两个点。(左儿子+右儿子)

所以我们可以类似滚动数组去做,用深度去更新,在更新完后此状态无用。

最后的代码在这里啦:


#include<bits/stdc++.h>
using namespace std;
int read() {
	char cc = getchar(); int cn = 0, flus = 1;
	while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
	while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
	return cn * flus;
}
#define int long long
const int N = 40000 + 5;
const int K = 45;
const int M = 105;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define ls(x) son[x][0]
#define rs(x) son[x][1]
int dp[M][K][K], son[N][3], fa[N], dep1[N], dep2[N]; 
int head[N], cnt, n, a[N], b[N], c[N];
void dfs( int x, int dep, int k ) {
	if( !son[x][0] ) {
		rep( i, 0, dep1[x] ) rep( j, 0, dep2[x] ) 
		dp[dep + k * dep][i][j] = c[x] * ( a[x] + i ) * ( b[x] + j );
		return ;
	}
	dep1[ls(x)] = dep1[x] + 1, dep2[ls(x)] = dep2[x]; //dep1表示公路 
	dep1[rs(x)] = dep1[x], dep2[rs(x)] = dep2[x] + 1; 
	dfs(ls(x), dep + 1, 0 ), dfs(rs(x), dep + 1, 1 );
	rep( i, 0, dep1[x] ) rep( j, 0, dep2[x] )
	dp[dep + k * dep][i][j] = min( dp[dep + 1][i][j] + dp[2 * (dep + 1)][i][j + 1], dp[dep + 1][i + 1][j] + dp[2 * (dep + 1)][i][j] );
}
signed main()
{
	n = read();
	rep( i, 1, n - 1 ) {
		son[i][0] = read(), son[i][1] = read();
		if( son[i][0] < 0 ) son[i][0] = -son[i][0] + n;
		if( son[i][1] < 0 ) son[i][1] = -son[i][1] + n;
	}
	rep( i, 1, n ) a[i + n] = read(), b[i + n] = read(), c[i + n] = read();
	dfs( 1, 0, 0 );
	printf("%lld
", dp[0][0][0] );
	return 0;
}

原文地址:https://www.cnblogs.com/Soulist/p/10629281.html