【树形dp小练】HDU1520 HDU2196 HDU1561 HDU3534

  • 【树形dp】就是在树上做的一些 dp 之类的递推,因为一般需要递归处理,因此平凡情况的处理可能需要理清思路。昨晚开始切了 4 题,作为入门训练。题目都非常简单,但是似乎做起来都还口以~

hdu1520 Anniversary party
  • 给一颗关系树,各点有权值,选一些点出来。任一对直接相连的边连接的点不能同时选择,问选择出的权值和最大为多少。
  • 考虑以 u 为根的子树可以选择的方法,dp[u]表示选择 u 时能获得最大收益,dp2[u]表示不选 u 时能获得的最大收益。则u不选时,为 dp2[u]=max{dp[v],dp2[v]}vu u 要选时,为dp[u]=valueu+max{dp2[v]}vu
/* **********************************************

  File Name: 1520.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月18日 星期二 19时50分55秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

const int INF = 0xfffffff;
const int MAX = 6007;
vector<int> G[MAX];
int val[MAX];
int dp[2][MAX];
bool vis[MAX];
int n;

void dfs(int u) {
    //printf("dfs %d\n", u);
    vis[u] = true;
    dp[0][u] = 0;
    dp[1][u] = val[u];
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        if (!vis[*it]) {
            dfs(*it);
            dp[1][u] += max(0, dp[0][*it]);
            dp[0][u] += max(0, max(dp[0][*it], dp[1][*it]));
        }
    }
}

int main() {
    while (cin >> n && n) {
        for (int i = 1; i <= n; ++i) {
            G[i].clear();
            cin >> val[i];
        }
        int x, y;
        for (int i = 1; i < n; ++i) {
            cin >> x >> y;
            G[x].push_back(y);
            G[y].push_back(x);
        }
        cin >> x >> y; //0 0
        //fill(dp[0], dp[0] + n + 1, -INF);
        //fill(dp[1], dp[1] + n + 1, -INF);

        memset(vis, false, sizeof(vis));
        dfs(1); //node 1 is root
        /*
        for (int i = 1; i <= n; ++i) {
            printf("dp[0][%d] = %d, dp[1][%d] = %d\n", i, dp[0][i], i, dp[1][i]);
        }
        */
        int ans = max(dp[0][1], dp[1][1]);
        if (ans == 0) {
            ans = -INF;
            for (int i = 1; i <= n; ++i) {
                if (ans < val[i]) {
                    ans = val[i];
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}

hdu2196 Computer

  • 一个树型网络,每个点代表一台 Computer ,每个点的 Maximum Distance 表示该点到树上其它所有点的距离中最长的。求
    i=1nMDistancei
  • 先预处理出各点深度。然后考虑点 u 的代价,即点u到其它某一点的最长路径,显然分为经过其父亲节点和不经过父亲节点两种。对于前者,我们在遍历时传递一个值 fd 给它,表示经过父亲节点到其它所有点的距离最长为多少,便可解决。对于后者,只需要考虑 u 到以u为根的子树中某一叶子的最长距离即可,于是可以通过 u 的孩子获得。下面说明怎么维护上述信息。
  • 对后一种情况,非常简单地就可以做到:对每个点维护一个值,表示以该点为根的树的高度,不妨表示为val1u,则 val1u=max{val1v+1}vu ,初始化为0.
  • 对前者,我们考虑 fd 首先必须包含 u 的父亲fa u 的这条边的长度,设为len1,然后有两种延伸方式,第一种是向 fa 的其它若干孩纸中的一个延伸下去,第二种是向 fa 的父亲延伸。第二种延伸显然已经无需额外维护,之前的递归已经处理完毕;第一种延伸则提示我们在搜索 fa 时,需要将 fa 得到的 fd fa 的其它孩纸的 val1 值进行对比,取较大者作为 fd 传递给 u ,也就是用其它孩纸的val1来动态地更新维护 fd 值。这样整个问题就做完了。
  • 注意输入
/* **********************************************

  File Name: 2196.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 20150819日 星期三 083506秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

/*
 * let dp[i][j]表示节点i的孩纸j所在子树的最大深度.
 * dp[i][j] = max{dp[j][k]} + 1.
 * Then. dfs后继遍历整棵树,父亲为root,遍历孩纸i时,
 * 传递max{max{dp[root][k]}(k!=i), fd}和一个深度标记。
 * 其中fd为root的父亲传递进来的值。深度标记需要修正。
 * 此次扫描即可得出每个点的花费
 */

typedef pair<int, int> P;
typedef pair<P, int> PP;
const int MAX = 10007;
vector<P> G[MAX];
multimap<P, int> M[MAX];
int cost[MAX];
bool vis[MAX];

int dfs(int u) {
    vis[u] = true;
    int res = 0;
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        if (!vis[it->first]) {
            int dep = dfs(it->first) + it->second;
            if (dep > res) res = dep;
            M[u].insert(PP(P(dep, it->first), it->second));
        }
    }
    return res;
}

void gao(int u, int fd) {
    //printf("gao %d, with %d\n", u, fd);
    //cost[u] = max{fd, M[u].rbegin()->first|M[u].rbegin()-1 ->first}
    cost[u] = max(fd, M[u].empty() ? 0 : M[u].rbegin()->first.first);
    //printf("cost[%d] = %d\n", u, cost[u]);
    if (M[u].empty()) {
        return;
    } else if (M[u].size() == 1) {
        auto it = M[u].begin();
        gao(it->first.second, it->second + fd);
    } else {
        for (auto it = M[u].begin(); it != M[u].end(); ++it) {
            for (auto it2 = M[u].rbegin(); it2 != M[u].rend(); ++it2) {
                if (it2->first.second != it->first.second) {
                    //printf("%d != %d\n", it2->first.second, it->first.second);
                    gao(it->first.second, it->second + max(fd, it2->first.first));
                    break;
                }
            }
        }
    }
}

int main() {
    int n;
    while (cin >> n) {
        for (int i = 1; i <= n; ++i) {
            G[i].clear();
            M[i].clear();
        }
        int x, y;
        for (int i = 2; i <= n; ++i) {
            cin >> x >> y;
            G[i].push_back(P(x, y));
            G[x].push_back(P(i, y));
        }
        memset(vis, false, sizeof(vis));
        dfs(1);
        /*
        for (int i = 1; i <= n; ++i) {
            printf("%d: ", i);
            for (auto it = M[i].begin(); it != M[i].end(); ++it) {
                printf("<%d,%d> ", it->first.second, it->first.first);
            }
            puts("");
        }
        */
        gao(1, 0);
        for (int i = 1; i <= n; ++i) {
            printf("%d\n", cost[i]);
        }
    }
    return 0;
}

hdu1561 The more, The Better

  • 首先简单分析一下。
    • 1. 将依赖关系视为有向边,则原关系网络为一个有向图。
    • 2. 其次,我们发现任意一个点的入度不会大于 1 ,也就是说,不会同时依赖于一个以上的点,原图似乎是一种拓扑的存在。
    • 3. 但是要注意,可能出现单向环,也就是出现类似: ab,bc,ca 的环,那么我们假设有这样一个环,可以发现:首先,环上任意一点必不可选择;其次,如果真的有这么一个环,由发现2可知不会有其它点指向环上任意一点,那么只需要考虑有向外指向的边,这很好处理,我们只需要 check 一下,任何能从该环开始扫描到的点都视为 invalid ,不合法。
  • 根据上面的分析,我们只需要先检查一遍,将所有在上述单向环上或者由某一个单向环可达的点检查出来,后续处理忽视它们即可。
  • 还有一个问题,原图可能是一颗森林,即入度为 0 的点似乎可能有多个。有一个比较精巧的做法是,令
    sum=1+i=1nvali
    增加点 0 ,且val0=sum,由点 0 向所有入度为0的点连边,求解当前这个图的 dp[0][M+1](sum+1) 即可。我们看看为什么可以这么做:
    • 首先,根节点权值非常大,因此一定可以被选到,一旦有一种方案不选它,收益为 v1 ,则方案只选择根节点的收益 v2=sum+1 一定大于 v1 ,所以 M+1 个点中一定有一个时根节点。把得到的最终方案中,根节点剔除掉,显然便是正解。而且输入数据对没有依赖的点给出的 a=0 也为这么做提供了方便
  • 处理时,令 dp[i][j] 表示以 i 为根,树规模为j的子树的最大收益。那么 dp[i][j]=max{dp[i][jk]+dp[a][k]}ai
/* **********************************************

  File Name: 1561.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月19日 星期三 10时59分42秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

const int MAX = 207;
const int INF = INT_MAX / MAX;
vector<int> G[MAX];
int val[MAX];
bool vis[MAX];
bool valid[MAX];
int dp[MAX][MAX];
int dp2[MAX];
int n, m;

void check(int u) {
    vis[u] = true;
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        if (vis[*it]) {
            valid[u] = false;
        }
        check(*it);
        if (!valid[*it]) valid[u] = false;
    }
}

/*
 * dp[i][j]: 以节点i为根(必选i)的j个城堡的最大收益.
 * dp[i][0] = 0;
 */
void dfs(int u) {
    dp[u][0] = 0;
    if (!valid[u]) {
        return;
    }
    dp[u][1] = val[u];
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        dfs(*it);
        memcpy(dp2, dp[u], sizeof(int) * MAX);
        for (int j = 2; j <= m + 1; ++j) {
            //printf("check %d\n", j);
            for (int k = 1; k < j; ++k) {
                dp2[j] = max(dp2[j], dp[*it][k] + dp[u][j - k]);
            }
        }
        memcpy(dp[u], dp2, sizeof(int) * MAX);
    }
}

int main() {
    while (cin >> n >> m && n) {
        for (int i = 0; i <= n; ++i) {
            G[i].clear();
        }
        int a, sum = 0;
        for (int i = 1; i <= n; ++i) {
            cin >> a >> val[i];
            G[a].push_back(i);
            sum += val[i];
        }
        val[0] = sum + 1;

        memset(valid, true, sizeof(valid));
        memset(vis, false, sizeof(vis));
        check(0);
        for (int i = 0; i <= n; ++i) {
            fill(dp[i], dp[i] + m + 2, -INF);
        }
        dfs(0);
        printf("%d\n", dp[0][m + 1] - sum - 1);
    }
    return 0;
}

hdu3534 Tree

  • 题意:问一颗树的直径 d 为多少,以及树上有多少路径长度为d
  • 首先此题没有给出数据范围,需要我们牺牲几次提交大概度量一下测试数据。检测发现,点数开到 10000 以上就足够了,所有边长度加起来不会超过 long long ,但会超过 int 范围。
  • 考虑树上的某一最长路径,设该路径各点中离根最近的为 u 。当u就是根节点时,路径长度等于树的高度。当 u 不为根节点,则考虑以u为根的子树,最长路径就是 u 到其所有孩子中距离最大的和次大的。分情况讨论即可。
  • 假设我们已经得出任何一点到其所有孩子中的最长距离dis[u][0]和次长距离 dis[u][1] 。则树的直径
    d=longest=max{dis[u][0]+dis[u][1]}
    对任何一点 u ,维护一颗map<long long, int>记录已经检查完毕的孩子的最大长度以及对应的路径数目,假设已经检查完其前 c1 个孩纸,当前检查第 c 个孩纸,最大长度len,那么只需要在 map 中查找是否存在 longestlen 的长度,如果存在,则组合一下,累加进答案即可。
/* **********************************************

  File Name: 3534.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 20150819日 星期三 135925秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAX = 10007;

struct Edge {
    int to;
    ll dis;
    Edge(int to = 0, int dis = 0): to(to), dis(dis) {}
};
vector<Edge> G[MAX];
map<ll, int> M[MAX];
ll dis[MAX][2];
ll longest;
int ans;
bool vis[MAX];

void dfs(int u) {
    vis[u] = true;
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        if (!vis[it->to]) {
            dfs(it->to);
            if (dis[u][0] < dis[it->to][0] + it->dis) {
                dis[u][1] = dis[u][0];
                dis[u][0] = dis[it->to][0] + it->dis;
            } else if (dis[u][1] < dis[it->to][0] + it->dis) {
                dis[u][1] = dis[it->to][0] + it->dis;
            }
        }
    }
    if (dis[u][0] + dis[u][1] > longest) {
        longest = dis[u][0] + dis[u][1];
    }
}

void dfs2(int u) {
    vis[u] = true;
    for (auto it = G[u].begin(); it != G[u].end(); ++it) {
        if (!vis[it->to]) {
            dfs2(it->to);
            int dis1 = M[it->to].rbegin()->first + it->dis;
            if (M[u].find(longest - dis1) != M[u].end()) {
                ans += M[u].find(longest - dis1)->second * M[it->to].rbegin()->second;
            }
            M[u][it->dis + M[it->to].rbegin()->first] += M[it->to].rbegin()->second;
            //printf("M[%d][%lld] += %d\n", u, it->dis + M[it->to].rbegin()->first, M[it->to].rbegin()->second);
        }
    }
    if (M[u].empty()) {
        M[u][0]++;
    }
}

int main() {
    ios::sync_with_stdio(false);
    int n;
    while (cin >> n) {
        for (int i = 1; i <= n; ++i) {
            G[i].clear();
            M[i].clear();
        }
        int u, v, len;
        for (int i = 1; i < n; ++i) {
            cin >> u >> v >> len;
            G[u].push_back(Edge(v, len));
            G[v].push_back(Edge(u, len));
        }
        memset(vis, false, sizeof(vis));
        memset(dis, 0, sizeof(dis));
        longest = 0LL;
        dfs(1);
        /*
        for (int i = 1; i <= n; ++i) {
            printf("dis[%d] = <%lld, %lld>\n", i, dis[i][0], dis[i][1]);
        }
        */
        //cout << "longest = " << longest << endl;
        memset(vis, false, sizeof(vis));
        ans = 0;
        dfs2(1);
        if (M[1].find(longest) != M[1].end()) {
            ans += M[1].find(longest)->second;
        }
        cout << longest << ' ' << ans << endl;
    }
    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值