下面来写一下几种基础DP的问题,本人菜鸡,有什么不对欢迎指正
最大字段和
题目:试题OJ
这个题我们用一个dp数组 dp[i] : 以第i个数结尾的最大字段和的最大值
很明显 有转移方程: dp[i] = max(dp[i-1] + a[i] , a[i]) 答案就是max(dp[i])啦
代码:
package 前缀与差分;
import java.io.*;
import java.util.Arrays;
public class P1115dp {
static int n;
static int dp[]; //dp[i] 以第i个元素结尾的序列的最大值 所以 dp[i]=max(dp[i-1]+a[i],a[i]);
static int a[];
public static void main(String[] args) throws IOException{
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pr = new PrintWriter(new OutputStreamWriter(System.out));
re.nextToken(); n = (int)re.nval;
a = new int[n+10];
dp = new int[n+10];
for(int i = 1;i <= n;i++){
re.nextToken();
a[i] = (int)re.nval;
}
int ans = -99999999;
Arrays.fill(dp, -9999999); // 题目存在负数,所以我们初始化dp数组为 -INF
for(int i = 1;i <= n;i++){
dp[i] = Math.max(a[i],dp[i-1]+a[i]);
ans = Math.max(ans, dp[i]);
}
pr.println(ans);
pr.flush();
}
}
最大加权矩形
题目: 试题OJ
最大加权矩形的模型其实就是最大字段和的一个变形,矩形要最大我们可以压缩这个矩形,得到一行数字,然后分别对这一行数字来求最大字段和即可,自己可以好好想想为什么矩形可以压缩成一行。
代码:
package 前缀与差分;
import java.io.*;
import java.util.Arrays;
public class P1719 {
static int n;
static int map[][] = new int[130][130];
static int t[] = new int[130]; // 保存每次压缩的数列
static int dp[] = new int[130];
static int ans = 0;
static void solve(){ // 子序列的和 用dp求 dp[i] 以i结尾最大子序列和
Arrays.fill(dp, -999999);
for(int i = 1;i <= n;i++){
dp[i] = Math.max(dp[i-1]+t[i],t[i]);
ans = Math.max(dp[i], ans);
}
}
static void push(){ // 压缩
for(int i = 1;i <= n;i++){
Arrays.fill(t, 0);
for(int j = i;j <= n;j++){
for(int k = 1;k <= n;k++)
t[k] += map[j][k];
solve();
}
}
}
public static void main(String[] args) throws IOException{
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pr = new PrintWriter(new OutputStreamWriter(System.out));
re.nextToken(); n = (int)re.nval;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++){
re.nextToken();
map[i][j] = (int)re.nval;
}
push();
pr.println(ans);
pr.flush();
}
}
最长上升子序列(LIS)
题目:
第一种方法:dp O(n^2) 试题OJ
dp[i] : 数列以第i个数结尾的最长上升子序列的最大值
可以得到转移方程
dp[i] = max(dp[1] ~ dp[i-1])当然a[i]得大于这里面的值才行
dp 边界:每个子序列最小为1 也就是 dp 初始值为1
代码:
package LIS;
import java.io.*;
import java.util.Arrays;
public class P502竞码 {
static int n;
static int a[] = new int[1010];
static int dp[] = new int[1010]; // dp[i]:以i为末尾最长上升子序列为多少 dp[i] = max(dp[1~i-1]+1) a[j]<a[i]
public static void main(String[] args) throws IOException{
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pr = new PrintWriter(new OutputStreamWriter(System.out));
re.nextToken(); n = (int)re.nval;
for(int i = 1;i <= n;i++){
re.nextToken();
a[i] = (int)re.nval;
}
Arrays.fill(dp,1);
for(int i = 1;i <= n;i++) // 求出每一个的dp[i]
for(int j = 1;j < i;j++)
if(a[i] > a[j])
dp[i] = Math.max(dp[j]+1,dp[i]);
int ans = 0;
for(int i = 1;i <= n;i++)
ans = Math.max(ans, dp[i]);
pr.println(ans);
pr.flush();
}
}
第二种方法 O(nlogn)
我们直接用一个数组(f [ ])来保存这个最长子序列长度,cnt 来表示这个数列的长度
我们每读入一个数,我们就判断这个数与 f[cnt] 的大小关系,如果比f[cnt]大,说明我们可以直接把这个数加入到数列最后面 即 f[++cnt] = a[i] ,如果小的话我们就把这个数边成数列中第一个大于他的数,具体为什么可以自己想想,模拟一下很快就懂啦,而我们的f数组是从小到大排序的 所以我们在找的时候可以使用二分,就可以达到 O(nlogn)的效果啦
这里有个题目 可以去试试,这个题是最长不下降子序列啊,大家要写的话加个等于就好啦 试题OJ
相对于这个试题的代码:
package 第四次模拟赛;
import java.io.*;
import java.util.*;
public class TestI {
static int n;
static int a[];
static int b[];
static int f[] = new int[100010];
static int cnt = 0;
static void bin(int x){
int l = 0,r = cnt;
int ans = 0;
while(l <= r){
int mid = (l+r)>>1;
if(f[mid] > x){
ans = mid;
r = mid-1;
}
else
l = mid+1;
}
f[ans] = x;
}
public static void main(String[] args) throws IOException{
StreamTokenizer re = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pr = new PrintWriter(new OutputStreamWriter(System.out));
re.nextToken(); n = (int)re.nval;
a = new int[n<<1|1];
b = new int[n<<1|1];
for(int i = 1;i <= n*2;i++){
re.nextToken();
a[i] = (int)re.nval;
b[i] = a[i];
}
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
Arrays.sort(b,1,2*n+1);
for(int i = 1;i <= n;i++){
map.put(b[i], i);
map.put(b[2*n-i+1],i);
}
for(int i = 1;i <= n*2;i++)
a[i] = map.get(a[i]);
//最长不下降子序列
for(int i = 1;i < a.length;i++){
if(a[i] >= f[cnt])
f[++cnt] = a[i];
else
bin(a[i]);
}
// for(int i = 1;i <= cnt;i++)
// pr.print(f[i]+" ");
pr.println(2*n-cnt);
pr.flush();
}
}
最长公共子序列(LCS)
题目: 试题OJ
代码:
定义dp[i][j] : 一个串长度为i 一个串长度为j 他们的最长公共子序列长度
我们分为2种情况
(1)如果当前 x串第i个字符等于 y 串第 j个字符 这个时候 dp[i][j] = dp[i-1][j-1] +1; 即选这个数
(2)如果不相等的话,我们判断是不要x串的字符 好些 还是不要y串的字符好些
即 dp[i][j] = max(dp[i-1][j],dp[i][j-1])
我们从1开始循环 就可以保证 i-1 j -1 这些值都已经算过
import java.util.*;
public class Main {
static char x[],y[];
static int dp[][] = new int[5001][5001]; // dp[i][j] 一个长度为i 一个长度为j
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
x = sc.next().toCharArray();
y = sc.next().toCharArray();
for(int i = 1;i <= x.length;i++)
for(int j = 1;j <= y.length;j++){
if(x[i-1] == y[j-1]) // 读入数据时从0开始的 故这里-1
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
System.out.println(dp[x.length][y.length]);
sc.close();
}
}
最大回文子串
题目:试题OJ
这个题正解区间DP
我们定义数组dp[i][j] : i ~ j 回文串的最长长度
我们枚举区间长度len,枚举起点 i,起点是i 那终点 j 自然就是 i+len-1
每一步我们可以选择的是
(1)选起点这个字符,也选终点这个字符(这就要保证这2个字符相同才能一起选)
(2)选起点这个字符,不选终点这个字符 dp[i[[j-1]
(3)不选起点这个字符,选终点这个字符 dp[i+1][j]
(4)不选起点这个字符,也不选终点这个字符 dp[i+1][j-1]
dp[i][j] 的取值就很明显了, 如果相等,就等于max(dp[i][j],dp[i+1][j-1]+2)
不相等的话 2 ,3 去最大即可
dp边界为 当长度为1的时候,我们的回文串长度也为1
代码: 相对于上面这个题的代码 ,如果直接求回文串的话 dp[0][a.length-1] 就已经是答案了
package lan7A;
import java.util.*;
public class P7008 {
static int N = 1010;
static int dp[][] = new int[N][N];// dp[i][j] i~j 最大回文子串长度
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char a[] = sc.next().toCharArray();
for(int len = 1;len <= a.length;len++){ // 枚举区间长度
for(int i = 0;i+len-1 < a.length;i++){ // 枚举区间起点不能超过总长
int j = i+len-1; // 区间终点
if(len == 1) dp[i][j] = 1; // 长度为1 回文串都是1
else{
dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
if(a[i] == a[j])
dp[i][j] = Math.max(dp[i][j],dp[i+1][j-1]+2);
}
}
}
System.out.println(a.length - dp[0][a.length-1]);
sc.close();
}
}
有问题欢迎指正,本弱鸡dp这方面菜的不行,,,