树型dp和博弈型dp问题

1. 树型dp.

package com.sata.dp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class TreeDp {
    /**
     * 圣诞party
     * Description
     *
     * 圣诞节马上就要来了,为了带动节日氛围并营造一个难得的交(tu)流(cao)机会,本部门3位很会浪的同事决定在外面包一个豪华别墅,并组织邀请部门同事一起轰趴欢度。
     *
     * 为了避免场面过于火爆,交(tu)流(cao)过于激烈,出现尴尬的局面,组织者决定,不能同时邀请某位同事和他的直属老板同时参加该盛宴。如果把整个部门的组织架构画出来,可以形成一颗树状结构,也就是说,只能挑这颗“树”上的部分节点参加。
     *
     * 为了进一步提升活动的效果,增强友谊,防止出现意外的言论,组织者决定尽可能邀请友好度较高的同事参加。于是他们开了一个周末的会,把部门所有同事都讨论了一遍,并且根据他们的观察,给每位同事打了一个友好分值。
     *
     * 那么组织者剩下的问题就是想在这个大树上,在上述限制条件下,邀请尽可能多的同事参加,并使得友好分值总和尽可能大。于是他们找到了你,希望你能帮他们解决这个问题并告诉他们最高可以能得多少分。
     *
     * 我们已经对数据进行了处理,从0开始顺序编码,每个同事都有一个ID。
     *
     *
     * Input
     * 输入包含三行:
     *
     * 第一行,N,表示同事总数,不超过1000。
     *
     * 第二行,N个正整数(0~100)之间,按ID顺序表示这n个同事的友好度分数,
     *
     * 第三行,N个整数(-1~N-1),按ID顺序表示此同事的上级ID,最大老板的上司ID为-1。
     *
     *
     * Output
     * 输出一个数字,最大值
     *
     *
     * Sample Input 1
     *
     * 7
     * 1 1 1 1 1 1 1
     * 2 2 4 4 -1 3 3
     * Sample Output 1
     *
     * 5
     * @param args
     */
    /**
     * 树形dp,员工之间的上下级关系hierarchy天然形成了一个tree, 相邻层级之间的节点存在互斥关系,标记每个node的两个状态:select/not select.
     * @param args
     */
    public static void main(String[] args) {
        int total = 0;
        try {
            total = Reader.nextInt();

            List scores = new ArrayList<>();
            List bosses = new ArrayList<>();
            for (int i = 0; i < total; i++) {
                scores.add(Reader.nextInt());
            }
            for (int j = 0; j < total; j++) {
                bosses.add(Reader.nextInt());
            }
            //relation map, tree.
            Map> relations = getRelations(bosses);
            int[] res = dfs(-1, relations.get(-1), relations, scores);
            System.out.println(Math.max(res[0], res[1]));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param node : current node
     * @param children : children nodes of current node
     * @param relations : overall relations.
     * @param scores : friendship score.
     * @return
     */
    private static int[] dfs(int node, List children, Map> relations, List scores) {
        int[] result = new int[2];
        if(children == null || children.isEmpty()) {
            result[1] += scores.get(node);
            return result;
        }
        List midVal = new ArrayList<>();
        for(int i : children) {
            int [] tmp = dfs(i, relations.get(i), relations, scores); //子节点继续dfs。
            midVal.add(tmp);
        }
        for(int[] tmp : midVal) {
            result[0] += Math.max(tmp[0], tmp[1]); //不选择该node, child nodes可选可不选
            result[1] += tmp[0]; //选择该node, 则child node不能被选
        }
        int score = node == -1? 0 : scores.get(node);
        result[1] += score; //选择该node,所以要加上它的score
        return result;
    }

    private static Map> getRelations(List bosses) {
        Map> mp = new HashMap<>(); //boss -> staffs
        for (int i = 0; i < bosses.size(); i++) {
            if (mp.get(bosses.get(i)) == null) {
                List l = new ArrayList<>();
                l.add(i);
                mp.put(bosses.get(i), l);
            } else {
                mp.get(bosses.get(i)).add(i);
            }
        }
        return mp;
    }
}

class Reader {
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static StringTokenizer tokenizer = new StringTokenizer("");

    static String nextLine() throws IOException {// 读取下一行字符串
        return reader.readLine();
    }

    static String next() throws IOException {// 读取下一个字符串
        while (!tokenizer.hasMoreTokens()) {
            tokenizer = new StringTokenizer(reader.readLine());
        }
        return tokenizer.nextToken();
    }

    static int nextInt() throws IOException {// 读取下一个int型数值
        return Integer.parseInt(next());
    }

    static double nextDouble() throws IOException {// 读取下一个double型数值
        return Double.parseDouble(next());
    }
}

2. 博弈型dp.

package com.sata.dp;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


/**
 * 拿红包
 * Description
 *
 * 临近年末了,因为部门在过去的一年表现非常优异,所以Johnson决定给大家派发红包来奖励大家过去一年的努力。为了展示对公司未来发展的信心,红包里面不是软妹币现金,而是公司的股票代券。因为微信红包不支持这样的交易,所以我们就只能用实体红包。为了让红包的派发更好玩一些,Johnson决定按如下方案派发。
 *
 * Johnson准备了大量的红包,每个红包里装有一定数量的代券,其数值代表某个最小单位(比如0.001股。具体数值不重要,但这个值是固定相同的)的倍数。红包的封面写明了具体数值,大家都能看到,假设是一个1~100的整数。
 *
 * 派发开始了,以两人为一组进行,Johnson将n个(n是偶数)红包一字摊开放在桌上。你和随机分配的搭(dui)档(shou)上场轮流拿红包,每人每次只能从最左边或者最右边拿一个红包,经过n/2轮后,正好拿完。
 *
 * 作为对公司前景充满信心的你,理所当然要尽一切努力拿到更多的股票代券。你搭档也一样,他/她也会尽一切努力去拿尽可能多的股票代券。
 *
 * 假设你先拿,比如桌上的红包是这样的情况(从左到右):100,42,3, 94, 3,71
 *
 * 那么你的最佳策略是:先拿100,你的搭档会拿71,你再拿42,你的搭档会再拿3,你再拿94,然后你的搭档拿最后剩下的3。这种策略下,你总共拿到236,搭档总共拿到77。搭档在你的阻击下,无法做得比这更好。你在搭档的阻击下,也无法做得更好了。
 *
 * 当桌上的红包数量一多,问题就变得复杂一些了,你们俩需要耗费更多的脑力进行博弈对抗。比如桌面上有:
 *
 * 94, 61, 29, 76, 23, 25, 37, 24, 1, 85, 98, 27
 *
 * 此时,你俩的最佳策略是:
 *
 * 左、L、右、R、右、L、左、L、左、L、左、last
 *
 * 其中中文代表你的选择,英文则代表搭档的选择。该策略下,你总共得到331,搭档则得到249。同样地,这是你俩激烈对抗下的最优解,你没法得到更多,你搭档也无法得到更多了。
 *
 * 你逐渐意识到,其实这些红包一摆到桌面上,你和搭档分别最多能拿多少就已经确定了(Johnson在后面偷笑,其实这就是他想给你们俩的股票数量),但需要你们俩都非常小心,每一步都不能走错,才能各自拿到最大值,否则就会让自己的一部分股票代券送给对方。
 *
 * 于是你拿出电脑,打开IDE,先算一下自己到底最多能拿到多少。
 *
 *
 * Input
 * 仅一行,代表桌上的红包情况,从左到右。
 *
 *
 * Output
 * 仅一行,代表你能拿到的最大数量。
 *
 *
 * Sample Input 1
 *
 * 50 32 66 90
 * Sample Output 1
 *
 * 140
 *
 * Sample Input 2
 *
 * 94 61 29 76 23 25 37 24 1 85 98 27
 * Sample Output 2
 *
 * 331
 *
 * Sample Input 3
 *
 * 71 49 28 63 52 100 60 68 61 78 70 13 14 92 43 70 48 42 71 8
 * Sample Output 3
 *
 * 605
 *
 * Sample Input 4
 *
 * 99 32 92 77 48 70 70 84 32 65 18 12 89 2 26 91 27 60 64 55 5 59 95 45 48 30 28 89 54 84
 * Sample Output 4
 *
 * 870
 */

/**
 * 博弈型dp, 对角线填充。
 */
public class GameDp {
    public static void main(String[] args) {
        Scanner in = new Scanner (System.in);
        String s = null;
        s = in.nextLine();
        String[] s2 = s.split(" ");
        List data = new ArrayList<>();
        for(int i = 0 ; i < s2.length ; i ++){
            data.add(Integer.valueOf(s2[i]));
        }
        System.out.println(helper(data));
        in.close();
    }
    private static int helper(List data) {
        int n = data.size();
        int[][][] dp = new int[n][n][2];
        //对角线初始化
        for(int i = 0; i < n; i++) { //数组长度为1,
            dp[i][i][0] = data.get(i); //先手拿掉,
            dp[i][i][1] = 0; //后手没有拿到
        }
        //沿对角线的enrich方式
        for(int l = 2; l <= n; l++) { //对角线填充完了,遍历数组长度,从对角线到上填充,因为必须保证i <= j
            for(int i = 0; i <= n-l; i++) {
                int j = i+l-1;
                // dp[i][j][0] 表示面对i->j位置的红包,先手能拿到的最大值
                int left = dp[i+1][j][1] + data.get(i); //拿了左边的红包,则面对(i+1 -> j)位置的红包,自己变成了后手
                int right = dp[i][j-1][1] + data.get(j); //拿了左边的红包,则面对(i -> j-1)位置的红包,自己变成了后手
                dp[i][j][0] = Math.max(left, right);
                //dp[i][j][1] 表示面对i->j位置的红包,后手能拿到的最大值
                if(left > right) {
                    dp[i][j][1] = dp[i+1][j][0]; //先手选择了左边,后手面对(i+1 -> j)位置的红包,变成了先手
                }else{
                    dp[i][j][1] = dp[i][j-1][0]; //先手选择了右边,后手面对(i -> j-1)位置的红包,变成了先手
                }
            }
        }
        return Math.max(dp[0][n-1][0], dp[0][n-1][1]);
    }
}

 

你可能感兴趣的:(树型dp和博弈型dp问题)