• 【树形dp小练】HDU1520 HDU2196 HDU1561 HDU3534


    • 【树形dp】就是在树上做的一些dp之类的递推,由于一般须要递归处理。因此平庸情况的处理可能须要理清思路。昨晚開始切了4题,作为入门训练。题目都很easy。可是似乎做起来都还口以~

    hdu1520 Anniversary party

    • 给一颗关系树,各点有权值。选一些点出来。任一对直接相连的边连接的点不能同一时候选择,问选择出的权值和最大为多少。

    • 考虑以u为根的子树能够选择的方法,dp[u]表示选择u时能获得最大收益。dp2[u]表示不选u时能获得的最大收益。则u不选时,为dp2[u]=max{dp[v],dp2[v]}vuu要选时。为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
    ", 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
    ", 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的孩子获得。以下说明怎么维护上述信息。

    • 对后一种情况,很easy地就能够做到:对每一个点维护一个值,表示以该点为根的树的高度,最好还是表示为val1u。则val1u=max{val1v+1}vu,初始化为0.
    • 对前者。我们考虑fd首先必须包括u的父亲fau的这条边的长度,设为len1。然后有两种延伸方式,第一种是向fa的其他若干孩纸中的一个延伸下去。另外一种是向fa的父亲延伸。另外一种延伸显然已经无需额外维护,之前的递归已经处理完成。第一种延伸则提示我们在搜索fa时,须要将fa得到的fdfa的其他孩纸的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 ", 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 ", 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 ", 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 ", 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
    ", 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
    ", 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
    ", 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>
    ", 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;
    }
  • 相关阅读:
    Math 对象
    String 对象-->split() 方法
    String 对象-->substring() 方法
    String 对象-->substr() 方法
    从零开始学 Web 之 移动Web(八)Less
    从零开始学 Web 之 移动Web(七)Bootstrap
    从零开始学 Web 之 移动Web(六)响应式布局
    从零开始学 Web 之 移动Web(五)touch事件的缺陷,移动端常用插件
    从零开始学 Web 之 移动Web(四)实现JD分类页面
    从零开始学 Web 之 移动Web(三)Zepto
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7162694.html
Copyright © 2020-2023  润新知