【Java】递推专题训练(动态规划)

题目链接

HENAU冬令营-递推专题
密码:202202060000

知识汇总

dp套路+优化思想
dp经典例题多类型详解
算法训练:数字三角形(含优化)
背包问题(多类型算法模板)
完全背包&多重背包

题目列表


快输模板

import java.io.*;
import java.util.*;
import java.util.regex.*;
public class Main {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }

        boolean hasNext() {
            if (st != null && st.hasMoreTokens())
                return true;
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    }

    static PrintWriter out = new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(System.out)));
    /*
    核心代码区
    */
}

A - 上台阶2

51nod 2116

	static long dp[];
    static int mod=100003;
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        dp=new long[n+1];
        for(int i=1;i<=n;i++){
            if(i<=2)dp[i]=1;
            else if(i==3)dp[i]=dp[i-1]+1;
            else if(i==4)dp[i]=dp[i-1]+dp[i-3];
            else if(i==5)dp[i]=dp[i-1]+dp[i-3]+1;
            else dp[i]=(dp[i-1]+dp[i-3]+dp[i-5])%mod;
        }
        out.println(dp[n]);
        out.flush();
    }

B - 数字三角形

B & C两题为同一类型题目,解法参考 :
算法训练:数字三角形(含优化)

	static int map[][];
    static int n;
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        map=new int[n+1][n+1];
        for(int i=1;i<=n;i++)
            for(int j=1;j<=i;j++)
                map[i][j]=sc.nextInt();
        dp();
        out.println(getMax());
        out.flush();
    }
    public static void dp(){
        for(int i=2;i<=n;i++)
            for(int j=1;j<=i;j++)
                map[i][j]=Math.max(map[i-1][j-1],map[i-1][j])+map[i][j];
    }
    public static int getMax(){
        int max=map[n][1];
        for(int i=2;i<=n;i++)
            if(map[n][i]>max) max=map[n][i];
        return max;
    }

C - 矩阵取数问题

	static int map[][];
    static int n;
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        map=new int[n+1][n+1];
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                map[i][j]=sc.nextInt();
        dp();
        out.println(getMax());
        out.flush();
    }
    public static void dp(){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                map[i][j]=Math.max(map[i-1][j],map[i][j-1])+map[i][j];
    }
    public static int getMax(){
        int max=map[n][1];
        for(int i=2;i<=n;i++)
            if(map[n][i]>max) max=map[n][i];
        return max;
    }

D - 背包问题(01背包)

递推打表法

	static int dp[],w[],p[];//w[]容积数组,p[]价值数组
    static int n,w0;//数量,容量
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        w0=sc.nextInt();
        //dp数组:放入容量为v的背包中的最大价值
        dp=new int[w0+1];
        w=new int[n+1];
        p=new int[n+1];
        for(int i=1;i<=n;i++){
            w[i]=sc.nextInt();
            p[i]=sc.nextInt();
        }
        for(int i=1;i<=n;i++){
            for(int v=w0;v>0;v--){
                if(v>=w[i])
                    dp[v]=Math.max(dp[v],dp[v-w[i]]+p[i]);
            }
        }
        out.println(dp[w0]);
        out.flush();
    }

暴力枚举法,以防MLE
HENAUOJ1259

	/**
     * 直接用动归会MLE,所以用暴力枚举
     */
    static int n,W;//数量,容量
    public static void main(String[] args) {
        W=sc.nextInt();
        n=sc.nextInt();
        Node[] data = new Node[n];
        for(int i=0;i<n;i++)data[i]=new Node(i+1,0,0);
        for(int i=0;i<n;i++)data[i].w=sc.nextInt();
        for(int i=0;i<n;i++)data[i].v=sc.nextInt();

        long vmax=0,wsum,vsum;
        int flag[]=new int[n];//判断是否添加此物品
        //1<<n n件物品,每件物品都有取或不取两种可能 总共由2^n种情况
        for(int i=0;i<1<<n;i++) {
            wsum=0;vsum=0;
            for(int j=0;j<n;j++) {
                if(flag[j]==0) {
                    flag[j]=1;
                    continue;
                }
                else {
                    flag[j]=0;
                    break;
                }
            }
            for(int j=0;j<n;j++) {
                if(flag[j]==1) {
                    wsum+=data[j].w;
                    vsum+=data[j].v;
                }
            }
            if(wsum<=W && vmax<vsum) {
                vmax=vsum;
            }
        }
        out.println(vmax);
        out.flush();
    }
class Node{
    int no,w,v;
    public Node(int no,int w,int v) {
        this.no=no;this.w=w;this.v=v;
    }
}

E - 完全背包

转自动态规划:完全背包、多重背包

0-1背包和完全背包的不同:
  从二维数组上区别0-1背包和完全背包也就是状态转移方程就差别在放第i中物品时,完全背包在选择放这个物品时,最优解是F[i][j-c[i]]+w[i]即画表格中同行的那一个,而0-1背包比较的是F[i-1][j-c[i]]+w[i],上一行的那一个。
  从一维数组上区别0-1背包和完全背包差别就在循环顺序上,0-1背包必须逆序,因为这样保证了不会重复选择已经选择的物品,而完全背包是顺序,顺序会覆盖以前的状态,所以存在选择多次的情况,也符合完全背包的题意。状态转移方程都为F[i]= max(F[i],dp[F-c[i]]+v[i])。

	static int dp[],w[],p[];//w[]容积数组,p[]价值数组
    static int n,w0;//数量,容量
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        w0=sc.nextInt();
        //dp数组:放入容量为v的背包中的最大价值
        dp=new int[w0+1];
        w=new int[n+1];
        p=new int[n+1];
        for(int i=1;i<=n;i++){
            w[i]=sc.nextInt();
            p[i]=sc.nextInt();
        }
        for(int i=1;i<=n;i++){
            for(int v=w[i];v<=w0;v++){
                if(v>=w[i])
                    dp[v]=Math.max(dp[v],dp[v-w[i]]+p[i]);
            }
        }
        out.println(dp[w0]);
        out.flush();
    }

F - 背包问题 V2

51nod 1086
解法参考:庆功会

	static int dp[],w[],p[];//w[]容积数组,p[]价值数组
    static int n,w0,n1;//数量,容量,计数器
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        w0=sc.nextInt();
        //dp数组:放入容量为v的背包中的最大价值
        dp=new int[w0+1];
        w=new int[2000010];
        p=new int[2000010];
        for(int i=1;i<=n;i++){
            int ww=sc.nextInt();//体积
            int pp=sc.nextInt();//价值
            int c=sc.nextInt();//第i件物品数量
            int t=1;
            while (c>=t) {
                w[++n1]=ww*t;//相当于n1++;  w[n1]=w[i]*t;
                p[n1]=pp*t;
                c-=t;
                t*=2;
            }
            w[++n1]=ww*c;
            p[n1]=pp*c;
        }
        for(int i=1;i<=n1;i++){
            for(int v=w0;v>=w[i]/*终止条件更改到for语句中*/;v--){
                dp[v]=Math.max(dp[v],dp[v-w[i]]+p[i]);
            }
        }
        out.println(dp[w0]);
        out.flush();
    }

G - 最长上升子序列

	//dp[n]:代表以第n个元素结尾的LIS序列的长度
    static int dp[],a[];
    static int n;
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        dp=new int[n];
        a=new int[n];
        for(int i=0;i<n;i++) a[i]=sc.nextInt();
        dp[0]=1;
        for(int i=1;i<n;i++) {
            int t=0;
            for(int j=0;j<i;j++) {
                if(a[j]<a[i]) {
                    if(t<dp[j])t=dp[j];
                }
            }
            dp[i]=t+1;
        }
        int t=0;
        for(int i=0;i<n;i++) {
            if(t<dp[i])t=dp[i];
        }
        out.println(t);
        out.flush();
    }

H - 最长公共子序列

dfs找最优解

	/*
	状态转移方程如下:
 	x[i]=y[i]:   dp[i][j]=dp[i-1][j-1]+1
	x[i]!=y[j]:  dp[i][j]=max(dp[i-1][j],dp[i][j-1])
	i=0||j=0:    dp[i][j]=0
	*/
	static char c1[],c2[];
    static int n,m;
    static int dp[][];
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        c1=(" "+sc.next()).toCharArray();
        c2=(" "+sc.next()).toCharArray();
        n=c1.length-1;m=c2.length-1;
        //dp[i][j]:字符串1的前i位和字符串2的前j位的LCS长度
        dp=new int[n+1][m+1];
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(c1[i]==c2[j]) {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else {
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        dfs(n,m);
        out.println();
        out.flush();
    }
    public static int dfs(int i,int j) {
        //字符串第一位均是空位,不算
        if(i==0||j==0) return 0;
        if(c1[i]==c2[j]){
            dfs(i-1,j-1);
            out.print(c1[i]);
        }
        else{
            if(dp[i-1][j]>dp[i][j-1])dfs(i-1,j);
            else dfs(i,j-1);
        }
        return 0;
    }

I - 石子合并(环形合并)

解法参考:洛谷P1880

	static int n,minl,maxl;//n堆石子
    //f[i][j]:表示从i到j堆石子合并后的最大f1/最小得分f2
    static int num[],s[],f1[][],f2[][];
    //转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
    //d(i,j)表示从i到j石子个数的和
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        int N=2*n+1;
        //因为是一个环,所以需要开到两倍再枚举分界线,最后肯定是最大的
        num=new int[N];
        s=new int[N];
        f1=new int[N][N];//max
        f2=new int[N][N];//min
        for(int i=1;i<=n+n;i++){
            if(i<=n){
                num[i]=sc.nextInt();
                num[i+n]=num[i];
            }
            s[i]=s[i-1]+num[i];
        }
        for(int p=1;p<n;p++) {
            for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p) {
                f2[i][j]=999999999;
                for(int k=i;k<j;k++)
                {
                    f1[i][j] = Math.max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
                    f2[i][j] = Math.min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
                }
            }
        }
        minl=999999999;
        for(int i=1;i<=n;i++)
        {
            maxl=Math.max(maxl,f1[i][i+n-1]);
            minl=Math.min(minl,f2[i][i+n-1]);
        }
        out.printf("%d\n%d",minl,maxl);
        out.flush();
    }
    public static int d(int i,int j){return s[j]-s[i-1];}

J - 循环数组最大子段和

找到最小的子段,数组和减去最小子段和,就是最大循环子段和
解法参考:51nod 1050

	public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        long sum=0;
        int num[]=new int[n+1];
        for(int i=0;i<n;i++){
            num[i]=sc.nextInt();
            sum+=num[i];
        }
        //样例超出int范围
        long dp[]=new long[n];
        for(int i=0;i<n;i++)//找最大子段
            if(i>0&&dp[i-1]>0)
                dp[i]=dp[i-1]+num[i];
            else
                dp[i]=num[i];
        long min=(int)1e9+7,max=0;
        for(int i=0;i<n;i++)
            if(max<dp[i])
                max=dp[i];
        for(int i=0;i<n;i++)//找最小子段
            if(i>0&&dp[i-1]<0)
                dp[i]=dp[i-1]+num[i];
            else
                dp[i]=num[i];
        for(int i=0;i<n;i++)
            if(min>dp[i])
                min=dp[i];
        min=sum-min;
        out.println(Math.max(min,max));
        out.flush();
    }

K - 没有上司的舞会

题目:51nod 2605
解法参考:树形dp总结
反思:
如果树形dp数组直接存list或者vector,会报以下编译错误。

注: : 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

查了一下,大概是这样的操作不安全,可能使程序崩溃。
改成用结点类封装list,再进行操作就编译通过了。
ps:如果用二维数组代替list的功能可能会MLE(甚至出现了RE :- ()

	//主程序
	static node tree[];
    static int ri[],vis[],f[][];//ri[]快乐指数
    static int n;//总人数
    //f[i][0]:i不来的情况下以i为根节点的子树的最大快乐值
    //f[i][1]:i来的情况下以i为根节点的子树的最大快乐值
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        n=sc.nextInt();
        ri=new int[n+1];
        vis=new int[n+1];
        tree=new node[n+1];
        f=new int[n+1][2];
        for(int i=1;i<=n;i++)ri[i]=sc.nextInt();
        for(int i=1;i<n;i++){
            int x=sc.nextInt();
            int y=sc.nextInt();
            if(tree[x]==null)tree[x]=new node();
            if(tree[y]==null)tree[y]=new node();
            tree[x].ad(y);
            tree[y].ad(x);
        }
        dfs(1);//从根节点找子树
        out.println(Math.max(f[1][0],f[1][1]));
        out.flush();
    }
    public static void dfs(int x){
        vis[x]=1;
        f[x][0]=0;
        f[x][1]=ri[x];
        for(int i=0;i<tree[x].length;i++){
            int son=tree[x].gt(i);
            if(vis[son]!=0)continue;
            dfs(son);
            f[x][0]+=Math.max(f[son][0],f[son][1]);
            f[x][1]+=f[son][0];
        }
    }
//结点类
class node{
    int length;
    List<Integer> ls;
    public node() {
        ls=new ArrayList<>();
        length=0;
    }
    public void ad(int x){
        ls.add(x);
        length++;
    }
    public int gt(int x){
        return ls.get(x);
    }
}

L - 滑雪

解法参考:滑雪

	static int n,m;//row & column
    static int dp[][]=new int[110][110];
    static int mp[][]=new int[110][110];//地图(高度)
    static int dir[][]={{1,0},{-1,0},{0,-1},{0,1}};//搜索方向
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        int i,j;
        n=sc.nextInt();
        m=sc.nextInt();
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                mp[i][j]=sc.nextInt();
        int s=0;
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                s=Math.max(find(i,j), s);
        out.printf("%d\n",s);
        out.flush();
    }
    public static int find(int x,int y) //找出从该点开始能进行的最大次数
    {
        if(dp[x][y]!=0)
            return dp[x][y];
        int d=1,tx,ty;
        for(int k=0;k<4;k++)
        {
            tx=x+dir[k][0];
            ty=y+dir[k][1];
            if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&mp[x][y]>mp[tx][ty])
                d=Math.max(find(tx,ty)+1,d);
        }
        dp[x][y]=d;
        return d;
    }

M - Palindrome

题意:求最少增加几个字符使得给定的字符串形成回文串
来源:HenauOJ1245
题解:源字符串插入最少字符生成回文串

 	/**题目:
     * 求最少增加几个字符使得给定的字符串形成回文串
     * 思路:
     * 转化为求最长回文子串的问题;最终结果为 原串长-最长回文子串的长度
     * 核心算法:最长公共子序列
     * dp[i][j]:字符串1的前i位和字符串2的前j位的LCS长度;本题中字符串1、2为同一字符串。
     */
    public static void main(String[] args) {
        FastReader sc = new FastReader();
        int n=sc.nextInt();
        char c[]=(" "+sc.next()).toCharArray();
        int dp[][]=new int[n+1][n+1];//dp[i][j] : 表示从 i 到 j 的最长回文子串长度
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(c[i]==c[n-j+1])dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
            }
        }
        out.println(n-dp[n][n]);
        out.flush();
    }

N - 最长公共子串

解法和最长公共子序列很相似,状态转移方程如下:

   i=0,j=0           dp[i][j]=0
   i,j>0 & xi=yj     dp[i][j]=dp[i-1][j-1]+1
   i,j>0 & xi!=yj    dp[i][j]=0

最长公共子串要求下标连续,最长公共子序列下标严格递增即可。

HENAUOJ1251

	public static void main(String[] args) {
        char c1[]=(" "+sc.next()).toCharArray();
        char c2[]=(" "+sc.next()).toCharArray();
        int n=c1.length-1;int m=c2.length-1;
        int dp[][]=new int[n+1][m+1];
        int max=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(c1[i]==c2[j])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=0;
                if(dp[i][j]>max)max=dp[i][j];
            }
        }
        out.println(max);
        out.flush();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值