动态规划教学,入门到入坑完整版,包含几十道例题

本文深入讲解动态规划的基本概念、适用场景及其在不同问题中的应用,包括背包问题、区间动态规划、状态压缩动态规划等,通过典型例题帮助理解并掌握动态规划的核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划

适用的题目类型:

1,多阶段决策问题
2,最优性原理
3,最优子结构特性

背包dp

01背包dp
核心代码

for(int j=0;j<=T;j++)//边界初始化 可以不需要
		{
			if(t[1]<=j) dp[1][j]=p[1];
		}
for(int i=2;i<=n;i++)
		{
			for(int j=T;j>=t[i];j--) //在能放进去的情况下找状态就行
			{
				dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-t[i]]+p[i]);
			}
		}

也可以用滚动数组 将二维dp降为一维dp

for(int i=1;i<=n;i++)
		{
			for(int j=T;j>=t[i];j--)
			{
				dp[j]=Math.max(dp[j], dp[j-t[i]]+p[i]);
			}
		}
		System.out.println(dp[T]);

完全背包dp和01很像,可以重复放入,不过要注意枚举顺序是正推的

for(int i=1;i<=n;i++)  //这是完全背包问题的!
		{
			for(int j=t[i];j<=T;j++)
			{
				dp[j]=Math.max(dp[j], dp[j-t[i]]+p[i]);
			}
		}

多重背包 转换成01背包就行了

多重背包也是 0-1 背包的一个变式。与 0-1 背包的区别在于每种物品 y 有  个,而非  个。
一个很朴素的想法就是:把「每种物品选  次」等价转换为「有  个相同的物品,每个物品选一次」。
这样就转换成了一个 0-1 背包模型,套用上文所述的方法就可已解决。
for(int i=1;i<=n;i++)  //这是多重背包问题的!
		{
			for(int j=T;j>=t[i];j--)
			{
				for(int k=1;k<=c[i];k++)//c[i]为第i个物品的总数量
				{
					if((j-t[i]*k)<0) break;
					dp[j]=Math.max(dp[j], dp[j-t[i]*k]+p[i]*k);
				}
				
			}
		}
		
		System.out.println(dp[T]);

多重背包优化:
将同一种物品的每一组合 单独列表(优化),后和01背包一样。

#include<iostream>
using namespace std;
const int N=20000;
int n,m;
int v[N],w[N];//存放同一种物品的每一组合
int dp[N];
int cnt;  //表示总个数


int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s-=k;
            k*=2;
        }
        if(s>0) 
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    for(int i=1;i<=cnt;i++)
        for(int j=m;j>=v[i];j--)
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[m];
    return 0;
}

0-1背包问题
描述:有n个物体,w(i),p(i) 分别为第i个物体的重量和价值,装进c容量的背包,求背包最大价值。

解题:
变量
n
w(i)
p(i)
g(i,j),在j容量的情况下,从前i个物体中挑选出来,背包的最大价值
g(i,j)={ g(i-1,j) w(i)>j;
{ max( g(i-1,j) g(i-1,j-w(i))+p(i)) w(i)<=j
初始化(可有可无): g(1,j)=p(1) { if(j=>w(1) }
g(1,j)=0 { if( j<=w(1)}

#include<iostream>

#define Equ(a,b) ((fabs((a)-(b)))<(eps)) //等于
#define More(a,b) (((a)-(b))>(esp)) //大于
#define Less(a,b) (((a)-(b))<(-esp))//小于
#define MoreEqu(a,b) (((a)-(b))>(-esp))//大于等于
#define LessEqu(a,b) (((a)-(b))<(esp))//小于等于

#define  MAX( x, y )  ( ((x) > (y)) ? (x) : (y) )//
//使用了algorithm头文件就可以直接使用max函数;
#define  MIN( x, y )  ( ((x) < (y)) ? (x) : (y) )
#define ll long long
#define PI 3.1415926
#define eps 1e-8
#define Conn(x,y) x##y

using namespace std;

const int maxx=0x7f7f7f7f;//0x7f7f7f7f表示数据的无穷大

int n;
int c;
int w[100];
int p[100];
int dp[100][100];

int main()
{
	cin>>n>>c;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>p[i];
	}
	for(int j=1;j<=c;j++)
	{
		if(w[1]<=j) dp[1][j]=p[1];
		else dp[1][j]=0;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=c;j++)
		{
			if(w[i]<=j&&dp[i-1][j]<(dp[i-1][j-w[i]]+p[i]))
			dp[i][j]=dp[i-1][j-w[i]]+p[i];
			else
			dp[i][j]=dp[i-1][j];
		}
	}
	cout<<dp[n][c];
	return 0;
}

区间动态

核心代码:三层循环

for(int len=2;len<=n;len++){//枚举区间长度
    for(int i=1;i<n;++i){//枚举区间的起点
        int j=i+len-1;//根据起点和长度得出终点
        if(j>n) break;//符合条件的终点
        for(int k=i;k<=j;++k)//枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
    }
}

题目和solution:
n石子合并: len i j k dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[k])
能量项链: len i j k dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[k])
矩阵取数: 每行 len left right dp[i][j]=max(dp[i-1][j]+a[i],dp[i][j-1]+a[n-j+1]

例题 石子合并
将堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 及每堆的石子数,并进行如下计算:

选择一种合并石子的方案,使得做 次合并得分总和最大。
选择一种合并石子的方案,使得做 次合并得分总和最小。
输入格式
输入第一行一个整数 ,表示有 堆石子。

第二行 个整数,表示每堆石子的数量。

输出格式
输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。


```java
```java
package apple;

import java.util.Scanner;

//2021年1月30日下午8:25:36
//writer:apple
public class sectiondptrue {

	
	static int n;
	static int a[]=new int[300];
	static int sum[]=new int[1000];
	static int ansmax[][]=new int [1000][1000];
	static int ansmin[][]=new int [1000][1000];
	static int max;
	static int min=Integer.MAX_VALUE;
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		n=scanner.nextInt();
		for(int i=1;i<=n;i++)
		{
			a[i]=scanner.nextInt();
			a[i+n]=a[i+n*2]=a[i+n*3]=a[i];
		}
		
		for(int i=1;i<=4*n;i++)
		{
			sum[i]=sum[i-1]+a[i];
		}				
		for(int len=2;len<=n;len++)
		{
			for(int i=1;i<=2*n-1;i++)
			{
				int j=i+len-1;
				if(j>=(n<<1)) break;
				ansmin[i][j]=Integer.MAX_VALUE;
				for(int k=i;k<j;k++)
				{
					ansmax[i][j]=Math.max(ansmax[i][j], ansmax[i][k]+ansmax[k+1][j]+sum[j]-sum[i-1]);
					ansmin[i][j]=Math.min(ansmin[i][j], ansmin[i][k]+ansmin[k+1][j]+sum[j]-sum[i-1]);
				}
			}
		}
		for(int i=1;i<=n;i++)
		{
			int j=i+n-1;
			max=Math.max(ansmax[i][j], max);
			min=Math.min(ansmin[i][j],min);
		}
		System.out.println(min);
		System.out.println(max);
	}

}

状态压缩动态规划(简称状压dp)

是另一类非常典型的动态规划,通常使用在NP问题的小规模求解中,虽然是指数级别的复杂度,但速度比搜索快,其思想非常值得借鉴。

为了更好的理解状压dp,首先介绍位运算相关的知识。

1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。

2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。

3.’’符号,xy,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)^2(10)=1(01)。

4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。

这四种运算在状压dp中有着广泛的应用,常见的应用如下:

1.判断一个数字x二进制下第i位是不是等于1。

方法:if ( ( ( 1 << ( i - 1 ) ) & x ) > 0)

将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。

2.将一个数字x二进制下第i位更改成1。

方法:x = x | ( 1<<(i-1) )

证明方法与1类似,此处不再重复证明。

3.把一个数字二进制下最靠右的第一个1去掉。

方法:x=x&(x-1)

感兴趣的读者可以自行证明。

位运算在状压dp中用途十分广泛,请看下面的例题。

描述
有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。根据上面的规则,我们知道1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;2)各个按钮被按下的顺序对最终的结果没有影响;3)对第1行中每盏点亮的灯,按下第2行对应的按钮,就可以熄灭第1行的全部灯。如此重复下去,可以熄灭第1、2、3、4行的全部灯。同样,按下第1、2、3、4、5列的按钮,可以熄灭前5列的灯。

输入
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。
输出
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。
样例输入
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
样例输出
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0

package algorithm_text;

import java.util.Scanner;

public class blank6 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
//		char []a=new char[]
		
		Scanner scanner=new Scanner(System.in);
		int [][] a=new int [6][8];
		int [][] ans=new  int[6][8];
		
		
		
		for(int i=1;i<=5;i++)
		{
			for(int j=1;j<=6;j++)
			{
				a[i][j]=scanner.nextInt();
			}
		}
		int c=1<<6;
		for(int i=1;i<=c;i++)
		{
			int flag=0;
			int k=i;
			for(int j=1;j<=6;j++)
			{
				int fk=k%2;
				k=k/2;
				ans[1][j]=fk;
			}
			
			for(int y=2;y<=5;y++)
			{
				for(int j=1;j<=6;j++ ) {
					ans[y][j]=a[y-1][j]^ans[y-1][j]^ans[y-1][j-1]^ans[y-1][j+1]^ans[y-2][j];
				}
			}
			for(int j=1;j<=6;j++)
			{
				if((a[5][j]^ans[5][j-1]^ans[5][j]^ans[5][j+1]^ans[4][j])==1)  {
					flag=1;break;
				}
			}
			if(flag==1) continue;
			else break;
		}
		for(int i=1;i<=5;i++)
		{
			for(int j=1;j<=6;j++)
			{
				System.out.print(ans[i][j]+" ");
			}
			System.out.println();
		}
	}

}

动态规划总结:

1,确认是使用二维dp还是一维dp
2,自行演算,从小范围到大范围的过程,确定dp状态转移方程
3,初始化未赋值的dp边界

例题:

完全背包变换题:

HDU1114:

package DP;

import java.util.Scanner;

//2021年4月15日下午10:53:54
//writer:apple
public class a6 {

	static int n;
	static int sumw;
	static int k;
	static int p[];
	static int w[];
	static long dp[];
	public static void main(String[] args) {
		// TODO Auto-generated method stub
//		10 110  可以装100g重量 
//		2
//		1(元) 1(克) 
//		30 50
		//两个30 价值60元 刚好 100克
		Scanner s=new Scanner(System.in);
		n=s.nextInt();
		for(int jj=0;jj<n;jj++)
		{	
			int w1=s.nextInt();int w2=s.nextInt();
			sumw=Math.abs(w1-w2);
			k=s.nextInt();
			p=new int[k+1];
			w=new int[k+1];
			dp=new long[10001];
			for(int i=1;i<=k;i++)
			{
				p[i]=s.nextInt();w[i]=s.nextInt();
			}
				for(int j=1;j<=10000;j++)
				dp[j]=Integer.MAX_VALUE;
			for(int i=1;i<=k;i++)
			{
				for(int j=w[i];j<=sumw;j++)
				{
					dp[j]=Math.min(dp[j],dp[j-w[i]]+p[i]);
				}
			}
			if(dp[sumw]==Integer.MAX_VALUE) System.out.println("This is impossible.");
			else {
				System.out.println("The minimum amount of money in the piggy-bank is "+dp[sumw]+".");
			}
		}
	}
}

最长递增子序列:

public class 最长递增子序列长度 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int  a[]= {0,4,5,7,3,2,8};//1 到 6
		int  dp[]=new int[7];//dp[i] 以i结尾的最大长度
		for(int i=1;i<7;i++) dp[i]=1;
		int maxlen=0;
		for(int i=1;i<=6;i++)
		{
			for(int j=0;j<i;j++)
			{
				if(a[i]>a[j])
				dp[i]=Math.max(dp[i],dp[j]+1);
			}
			maxlen=Math.max(maxlen,dp[i]);
		}
		System.out.println(maxlen);
	}
}

选n个人排列,求有多少种排法

只要求,女生不能单独排,女生,男生数量都无限

package dp;

import java.util.Scanner;

//2021年3月13日下午1:38:08
//writer:apple
public class dong1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int dp[][]=new int[n+1][2];
		dp[1][0]=1;dp[1][1]=0;
		dp[2][0]=1;dp[2][1]=1;
		dp[3][0]=2;dp[3][1]=2;
		for(int i=4;i<=n;i++)
		{
			//n个人  女生不能单独排列 0男生  1女生 
			dp[i][0]=dp[i-1][0]+dp[i-1][1];
			dp[i][1]=dp[i-2][0]+dp[i-2][1]+dp[i-3][0]+dp[i-3][1];
		}
		int ans=dp[n][0]+dp[n][1];
		System.out.println(ans);
		
		//或者用一维数组
//		int dp[]=new int[n+1];
//		dp[1]=1;dp[2]=2;dp[3]=4;
//		for(int i=4;i<=n;i++)
//		{
//			dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
//		}
//		int ans=dp[n];
//		System.out.println(ans);
	}
}

> 数字三角形(POJ1163)

 在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
 路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。
  三角形的行数大于1小于等于100,数字为 0 - 99


输入格式:

//表示三角形的行数 接下来输入三角形

5     

7

3   8

8   1   0

2   7   4   4

4   5   2   6   5

要求输出最大和

可以从底部开始算,再到顶部,为逆推
也可以从顶部算,为顺推,下面java代码有
dp[i][j] 表示第i行j列到最底边的最大和。

#include<iostream>

#define Equ(a,b) ((fabs((a)-(b)))<(eps)) //等于
#define More(a,b) (((a)-(b))>(esp)) //大于
#define Less(a,b) (((a)-(b))<(-esp))//小于
#define MoreEqu(a,b) (((a)-(b))>(-esp))//大于等于
#define LessEqu(a,b) (((a)-(b))<(esp))//小于等于

#define  MAX( x, y )  ( ((x) > (y)) ? (x) : (y) )//
//使用了algorithm头文件就可以直接使用max函数;
#define  MIN( x, y )  ( ((x) < (y)) ? (x) : (y) )
#define ll long long
#define PI 3.1415926
#define eps 1e-8
#define Conn(x,y) x##y

using namespace std;

const int maxx=0x7f7f7f7f;//0x7f7f7f7f表示数据的无穷大

const int mm=1000;

int n;
int dp[mm][mm];
int a[mm][mm];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i];
		}
	}
	cout<<dp[1][1];
	return 0;
}

用java写,顺推,逆推都有

package dp;

import java.util.Scanner;

//2021年3月13日下午2:36:53
//writer:apple
public class dong3 {
//路径权值最大
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();
		int a[][]=new int[n+1][n+1];
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=i;j++)
			{
				a[i][j]=scanner.nextInt();
			}
		}
		
		
//		从上往下顺推 dp[i][j] 代表顶点到i,j的最大值
//		int dp[][]=new int[n+1][n+1];
//		dp[1][1]=a[1][1];
//		for(int i=2;i<=n;i++)
//		{
//			for(int j=1;j<=i;j++)
//			{
//				dp[i][j]=Math.max(dp[i-1][j-1], dp[i-1][j])+a[i][j];
//			}
//		}
//		int ans=0;
//		for(int i=1;i<=n;i++)
//		{
//			int t=dp[n][i];
//			if(t>ans)
//			{
//				ans=t;
//			}
//		}
		
//		从下往上逆推
		int dp[][]=new int[n+2][n+2];//dp[i][j]代表 从最底部到i,j的最大值
		for(int i=n;i>=1;i--)
		{
			for(int j=1;j<=i;j++)
			{
				dp[i][j]=Math.max(dp[i+1][j], dp[i+1][j+1])+a[i][j];
			}
		}
		int  ans=dp[1][1];
		System.out.println(ans);

	}

}

G - 免费馅饼

HDU - 1176
都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标:

为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)
Input
输入数据有多组。每组数据的第一行为以正整数n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。
Output
每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。

Sample Input
6
5 1
4 1
6 1
7 2
7 2
8 3
0
Sample Output
4

package dp;

import java.util.Scanner;

//2021年3月14日上午12:22:08
//writer:apple
public class dong4_2 {

	static int dp[][]=new int[100005][14];//dp[i][j] 代表第i秒,j位置上最多的饼数量
	static int map[][]=new int[100005][14];//map[i][j]代表 第i秒 的j位置上有的饼数量
	public static void main(String[] args) {
		// TODO Auto-generated method stub
Scanner scanner=new Scanner(System.in);
		
		while(true)
		{
			int n=scanner.nextInt();//代表有多少个饼
			if(n==0 ) break;
			int flag;
			int maxt=0;
			init();
			for(int i=1;i<=n;i++)
			{
				int weizhi=scanner.nextInt();int time=scanner.nextInt();
				map[time][weizhi]++;
				if(time>maxt) maxt=time;
			}
//			for(int j=0;j<=10;j++)
//			{
//				dp[maxt][j]=map[maxt][j];
//			}
			for(int i=maxt;i>=0;i--)
			{
				for(int j=0;j<=10;j++)
				{
					
					if(j-1<=0) {
						dp[i][j]=Math.max(Math.max(0, dp[i+1][j]),dp[i+1][j+1])+map[i][j];
					}
					else {
						dp[i][j]=Math.max(Math.max(dp[i+1][j-1], dp[i+1][j]),dp[i+1][j+1])+map[i][j];
					}
					
				}
			}
			
			System.out.println(dp[0][5]);
			
			
		}

	}
	public static void init()
	{
		for(int i=0;i<=100000;i++)
		{
			for(int j=0;j<=10;j++)
			{
				dp[i][j]=0;
				map[i][j]=0;
			}
		}
	}

}

搬寝室

题目描述:
搬寝室是很累的,xhd深有体会.时间追述2006年7月9号,那天xhd迫于无奈要从27号楼搬到3号楼,因为10号要封楼了.看着寝室里的n件物品,xhd开始发呆,因为n是一个小于2000的整数,实在是太多了,于是xhd决定随便搬2k件过去就行了.但还是会很累,因为2k也不小是一个不大于n的整数.幸运的是xhd根据多年的搬东西的经验发现每搬一次的疲劳度是和左右手的物品的重量差的平方成正比(这里补充一句,xhd每次搬两件东西,左手一件右手一件).例如xhd左手拿重量为3的物品,右手拿重量为6的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.现在可怜的xhd希望知道搬完这2*k件物品后的最佳状态是怎样的(也就是最低的疲劳度),请告诉他吧。

输入:
每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数).

输出:
对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.

样例输入:
2 1
1 3
样例输出:
4

package dp;
import java.util.Arrays;
import java.util.Scanner;
//2021年3月13日下午3:20:21
//writer:apple
public class dong6 {
	//搬寝室
	//求最小疲劳度  搬一趟(zuo-you)的二次方
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner=new Scanner(System.in);
		int n=scanner.nextInt();int k=scanner.nextInt();//n个物体  k趟
		int a[]=new int[n+1];
		for(int i=1;i<=n;i++)
		{
			a[i]=scanner.nextInt();
		}
		int dp[][]=new int[n+1][k+1];//dp[i][j] 有i个物品 j趟
		Arrays.sort(a,1,n+1);//升序
		dp[0][0]=0;dp[1][0]=0;
		dp[1][1]=0;
		for(int j=1;j<=k;j++)
		{
			for(int i=j*2;i<=n;i++)
			{
				if(i==2) dp[i][j]=fang(a,1,2);
				else {
					dp[i][j]=Math.min(dp[i-1][j],dp[i-2][j-1]+fang(a,i,i-1));
				}
			}
		}
		System.out.println(dp[n][k]);
	}
	public static int fang(int x[],int i,int j)
	{
		return (x[i]-x[j])*(x[i]-x[j]);
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值