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]);
}
}