POJ 1155 TELE (树状DP)

来源:互联网 发布:如何用dos运行java 编辑:程序博客网 时间:2024/05/12 07:12
题目类型  树状DP

题目意思
给出 n 个点 (2 <= n <= 3000) 和 一个数 m (m 表示 n 个点中有多少个叶子结点)
然后给出每一个非叶子结点与其他点相连的边 这些点和边构成一棵树
非叶子结点与叶子结点之间的边在计算时是加的 而非叶子结点之间的边在计算时是减的
现在问从根 1 可以到达最多多少个叶子结点 (要求根到这些叶子结点的路径权值加起来非负, 相同的边只计算一次)

解题方法
树状DP
dp[i][j] -> 表示以 i 结点为根去到 j 个叶子结点的路径权值和加起来最大是多少
那么用子结点 v 去优化父结点 u 的转移方程如下
dp[u][j] = max(dp[u][j], dp[u][j-k] + dp[v][k] - W[u][v]); 其中 (1<=j<=子树u拥有的叶子结点数) (1<=k<=子树v拥有的叶子结点数) 
即像01背包那样用以子结点为根的子树的一部分去更新父结点 这里要考虑重复使用一部分去更新的情况 详细见代码

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>#include <cstdio>#include <cstring>#include <vector>using namespace std;const int INF = 1<<29;int dp[3010][3010];int num[3010];vector<int>M[3010];vector<int>W[3010];int in[3010], n, m;int tmp[3010];void DFS(int u, int fa) {if(u > n-m) { // 如果结点 u 是叶子结点的话 直接把与u相连的边的权值赋给 dp[u][1] dp[u][1] = in[u];return ;}for( int i=0; i<M[u].size(); i++ ) { // 枚举 u 的子结点int v = M[u][i];if(v == fa) continue;DFS(v, u);for( int j=1; j<=num[u]; j++ ) tmp[j] = dp[u][j]; // 先保存结点 u 的数据 避免某一部分重复更新for( int j=1; j<=num[v]; j++ ) { // 相当于01背包中的 num[v]件物品 每件物品的体积是 j 价值是 dp[v][j]for( int k=num[u]; k>=j; k-- ) {dp[u][k] = max(dp[u][k], tmp[k-j]+dp[v][j] - W[u][i]); // 如果不使用 tmp数组的话 子结点v的某一比较优的部分会重复使用}}}}void Init(int u, int fa) { // 求以 u 为根的子树包含的叶子结点数if(u > n-m) {num[u] = 1;return ;}num[u] = 0;for( int i=0; i<M[u].size(); i++ ) {int v = M[u][i];if(v == fa) continue;Init(v, u);num[u] += num[v];}}int main() {freopen("in", "r", stdin);while(scanf("%d%d", &n, &m) != EOF) {for( int i=1; i<=n; i++ ) for( int j=0; j<=n; j++ ) dp[i][j] = -INF;for( int i=1; i<=n; i++ ) dp[i][0] = 0;for( int i=1; i<=n-m; i++ ) M[i].clear(), W[i].clear();for( int i=1; i<=n-m; i++ ) {int k;scanf("%d", &k);for( int j=0; j<k; j++ ) {int v, w;scanf("%d%d", &v, &w);M[i].push_back(v);W[i].push_back(w);}}for( int i=n-m+1; i<=n; i++ ) scanf("%d", &in[i]);Init(1, 0);DFS(1, 0);int ans = 0;for( int i=m; i>=0; i-- ) if(dp[1][i] >= 0) { ans = i; break; }printf("%d\n", ans);}return 0;}


0 0
原创粉丝点击