文章目录
题目链接
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
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
最长公共子串要求下标连续,最长公共子序列下标严格递增即可。
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();
}