提交 478ec6aa 编写于 作者: qq_36480062's avatar qq_36480062

c

上级 9ed7a7a8
......@@ -51,4 +51,4 @@ public class 重复性剪枝 {
}
}
}
}
}
\ No newline at end of file
......@@ -5,12 +5,19 @@ import static java.lang.System.in;
public class IO加速 {
public static void main(String[] args) throws IOException {
//标准输入流
int i = nextInt();
System.out.println(i);
tokenizer=new StringTokenizer("123123 15412 4312412");
System.out.println(tokenizer.nextToken());
System.out.println(tokenizer.nextToken());
System.out.println(tokenizer.hasMoreTokens());
System.out.println(tokenizer.nextToken());
System.out.println(tokenizer.hasMoreTokens());
//标准输出流,只能输出字符串,不能输出数字!!!
bw.write(i + " ");
bw.write(1 + " ");
bw.flush();
}
......@@ -24,6 +31,7 @@ public class IO加速 {
static String next() throws IOException {// 读取下一个字符串
while (!tokenizer.hasMoreTokens()) {
//如果没有字符了,就是下一个,使用空格拆分,
tokenizer = new StringTokenizer(reader.readLine());
}
return tokenizer.nextToken();
......
package String;
import java.util.Scanner;
/**
* https://blog.csdn.net/qq_30277239/article/details/100881221
*/
public class KMP {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
a = sc.next().toCharArray();
m = sc.nextInt();
p = sc.next().toCharArray();
}
static char[] a, p;
static int n, m;
static int[] ne = new int[100005];
//next数组
static void init() {
ne[0] = -1;
int t = 0;
for (int i = 1; i <= n; i++) {
t = ne[i - 1];
while (t != -1 && p[i - 1] != p[t]) t = ne[t];
ne[i] = t + 1;
}
}
}
......@@ -55,20 +55,19 @@ public class 单调队列 {
for (int i = 0; i < n; i++) {
a[i] = nextInt();
}
List();
// int head = 0, end = -1;
// for (int i = 0; i < n; i++) {
// if (head <= end && q[head] < i - k + 1) head++;
// //队列里面的元素不在窗口内,就删除该元素
// //i-k+1是当前窗口的第一个值的下标
// while (head <= end && a[q[end]] >= a[i]) end--;
// //如果队列尾部的值大于新加进来的值,就删除队尾元素,
// //对应的是队尾指针前移
// q[++end] = i;
// //把新元素插入到队尾
// if (i >= k - 1) System.out.println(a[q[head]]);
// }
// List();
int head = 0, end = -1;
for (int i = 0; i < n; i++) {
if (head <= end && q[head] < i - k + 1) head++;
//队列里面的元素不在窗口内,就删除该元素
//i-k+1是当前窗口的第一个值的下标
while (head <= end && a[q[end]] >= a[i]) end--;
//如果队列尾部的值大于新加进来的值,就删除队尾元素,
//对应的是队尾指针前移
q[++end] = i;
//把新元素插入到队尾
if (i >= k - 1) System.out.println(a[q[head]]);
}
}
......
package greedy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Scanner;
/**
* 给定N个闭区间[ai,bi],请你将这些区间分成若干组,
* 使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
......@@ -13,16 +18,46 @@ package greedy;
* 1≤N≤10^5,
* −10^9≤ai≤bi≤10^9
* 输入样例:
* 3
* -1 1
* 2 4
* 3 5
* 3
* -1 1
* 2 4
* 3 5
* 输出样例:
* 2
*
*/
public class 区间分组 {
public static void main(String[] args) {
static class node implements Comparable<node> {
int x, y;
public node(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(node node) {
return x - node.x;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 0; i < n; i++) {
a.add(new node(sc.nextInt(), sc.nextInt()));
}
Collections.sort(a);
q.add(a.get(0).y);
for (int i = 1; i < n; i++) {
if (q.size() != 0 && q.peek() < a.get(i).x) {
q.poll();
}
q.add(a.get(i).y);
}
System.out.println(q.size());
}
static PriorityQueue<Integer> q = new PriorityQueue<Integer>();
static ArrayList<node> a = new ArrayList<node>();
static int n, ans;
}
package greedy;
import java.util.PriorityQueue;
import java.util.Scanner;
/**
* 在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。
* 达达决定把所有的果子合成一堆。每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。
* 可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。
* 达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。
* 因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。
* 假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,
* 你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。
* 例如有3种果子,数目依次为1,2,9。
* 可以先将1、2堆合并,新堆数目为3,耗费体力为3。
* 接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。
* 所以达达总共耗费体力=3+12=15。
* 可以证明15为最小的体力耗费值。
* 输入格式
* 输入包括两行,第一行是一个整数n,表示果子的种类数。
* 第二行包含n个整数,用空格分隔,第i个整数ai是第i种果子的数目。
* 输出格式
* 输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。
* 输入数据保证这个值小于2^31。
* 数据范围
* 1≤n≤10000,
* 1≤ai≤20000
* 输入样例:
* 3
* 1 2 9
* 输出样例:
* 15
* <p>
* 本题考查哈夫曼树,每次选择权值最小的两个节点合并为新节点,
* 直到最后所有节点都合并为了一个节点,这样求得的带权路径和是最小的。
* 可以使用小根堆实现,先将所有节点加入到小根堆中,
* 然后每次取出最小的两个节点并从堆中删除,最后将取出的两个节点合并为新的结点加入到小根堆中,
* 重复该过程直至小根堆中只剩下一个节点为止。
* 可以推导出贪心选择性质:先合并小的,花费最小
*/
public class 合并果子 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 0; i < n; i++) {
q.add(sc.nextInt());
}
int x, y, res = 0;
while (q.size() > 1) {
x = q.poll();
y = q.poll();
q.add(x + y);
res += x + y;
}
System.out.println(res);
}
static PriorityQueue<Integer> q = new PriorityQueue<Integer>();
static int n;
}
package 数位dp;
/**
* Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy数。
* Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 Windy 数?
* 输入格式
* 共一行,包含两个整数 A 和 B。
* 输出格式
* 输出一个整数,表示答案。
* 数据范围
* 1≤A≤B≤2×10^9
* 输入样例1:
* 1 10
* 输出样例1:
* 9
* 输入样例2:
* 25 50
* 输出样例2:
* 20
*
*/
public class Windy数 {
public static void main(String[] args) {
}
}
package 数位dp;
/**
* 数位dp的技巧
*/
public class 度的数量 {
public static void main(String[] args) {
}
}
###数位dp
````
某一个区间[x,y]里,符合某种条件(满足某种性质)的数一共有多少个
技巧1:
设f(y)表示 1~y 中所有所有满足条件的个数
设f(x-1)表示 1~x-1 所有所有满足条件的个数
[x,y]->f(y)-f(x-1)
类似于前缀和
技巧2:
使用树的角度来考虑
package 数位dp;
import java.util.ArrayList;
import java.util.Scanner;
/**
* 科协里最近很流行数字游戏。
* 某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。
* 现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
* 输入格式
* 输入包含多组测试数据。
* 每组数据占一行,包含两个整数 a 和 b。
* 输出格式
* 每行给出一组测试数据的答案,即 [a,b]之间有多少不降数。
* 数据范围
* 1≤a≤b≤2^31−1
* 输入样例:
* 1 9
* 1 19
* 输出样例:
* 9
* 18
*/
public class 数字游戏 {
public static void main(String[] args) {
init();
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int s = sc.nextInt();
int t = sc.nextInt();
System.out.println(get(t) - get(s - 1));
}
}
static int N = 12;
static int[][] f = new int[N][N];
static void init() {
for (int i = 0; i <= 9; i++) {
f[1][i] = 1;
}
for (int i = 2; i < N; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = j; k <= 9; k++) {
f[i][j] += f[i - 1][k];
}
}
}
}
static int get(int n) {
if (n < 10) return n + 1;
ArrayList<Integer> num = new ArrayList<Integer>();
while (n != 0) {//取出n的每一位
num.add(n % 10);
n /= 10;
}
int res = 0, last = 0;
for (int i = num.size() - 1; i >= 0; i--) {
int x = num.get(i);
for (int j = last; j < x; j++) {
res += f[i + 1][j];
}
if (x < last) break;
last = x;
if (i == 0) res++;
}
return res;
}
}
package 树形dp;
public class 二叉苹果树 {
public static void main(String[] args) {
}
static int n, m, cnt = 1;
static int[] he = new int[100];
static int[] ne = new int[100];
static int[] e = new int[100];
static void add(int a, int b) {
e[cnt] = b;
ne[cnt] = he[a];
he[a] = cnt++;
}
}
package 树形dp;
import java.util.Arrays;
import java.util.Scanner;
/**
......@@ -37,19 +36,36 @@ import java.util.Scanner;
* 输出样例:
* 1
* 2
* 显然改个样例
* 4 3 四个点3条边无向图
* 0 1
* 1 3
* 1 2
* <p>
* 5 4
* 3 1
* 3 4
* 3 2
* 1 0
*/
public class 战略游戏 {
public static void main(String[] args) {
System.out.println();
Scanner sc = new Scanner(System.in);
int t, w;
while (sc.hasNext()) {
n = sc.nextInt();
Arrays.fill(vis, false);
cnt = 1;
for (int i = 0; i < n; i++) {
t = sc.nextInt();
int t, w, e;
n = sc.nextInt();
e = sc.nextInt();
for (int i = 0; i < e; i++) {
t = sc.nextInt();
w = sc.nextInt();
add(t, w);
vis[w] = true;
}
for (int i = 0; i < n; i++) {
if (!vis[i]) {
dfs(i);
System.out.println(Math.min(f[i][1], f[i][0]));
break;
}
}
}
......
......@@ -25,6 +25,13 @@ import java.util.Scanner;
* 6 1 7
* 输出样例:
* 22
* 基础做法:任取一点,找到离该点最远距离的点u,
* 然后从u出发找到离u最远的点v,则u-v就是树的直径
* 证明思路:只需要证明任取一点,找到离该点最远距离的点u,点u是直径的一个端点
* 显然,u-v就是树的直径
* 推导过程
* https://blog.csdn.net/qq_30277239/article/details/104396460?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158864856319725247611541%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=158864856319725247611541&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v25-1
*
*/
public class 树的直径 {
public static void main(String[] args) {
......
package 状态压缩dp;
import java.util.ArrayList;
import java.util.Scanner;
/**
* https://blog.csdn.net/qq_30277239/article/details/104210900
* 如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
* 图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
* 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,
* 即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
* 输入格式
* 第一行包含两个由空格分割开的正整数,分别表示N和M;
* 接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。
* 输出格式
* 仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
* 数据范围
* N≤100,M≤10
* 输入样例:
* 5 4
* PHPP
* PPHH
* PPPP
* PHPP
* PHHP
* 输出样例:
* 6
*/
public class 炮兵阵地 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
String tem;
int c = 0;
for (int i = 1; i <= n; i++) {
tem = sc.next();
c = 0;
for (int j = m - 1; j >= 0; j--) {
if (tem.charAt(c++) == 'H') g[i] += 1 << j;
}
System.out.println(Integer.toBinaryString(g[i]));
}
for (int i = 0; i < 1 << m; i++) {
if (judge(i)) {
a.add(i);
cnt[i] = count(i);
}
}
int s = 0, t = 0, r = 0;
//第i行,状态为j
for (int i = 1; i <= n; i++) {
int u = i & 1, v = u == 0 ? 1 : 0;
for (int j = 0; j < a.size(); j++) {
if ((g[i] & a.get(j)) != 0) continue;
for (int k = 0; k < a.size(); k++) {
if ((g[i - 1] & a.get(k)) != 0) continue;
for (int l = 0; l < a.size(); l++) {
if (i > 2 && (g[i - 1] & a.get(l)) != 0) continue;
s = a.get(j);
t = a.get(k);
r = a.get(l);
if ((s & t | s & r | t & r) == 0) f[u][j][k] = Math.max(f[u][j][k], f[v][k][l] + cnt[s]);
}
}
}
}
int res = 0, u = n & 1;
for (int i = 0; i < a.size(); i++) {
for (int j = 0; j < a.size(); j++) {
res = Math.max(res, f[u][i][j]);
}
}
System.out.println(res);
}
static int[][][] f = new int[2][1 << 11][1 << 11];
static int[] cnt = new int[1 << 11];
static ArrayList<Integer> a = new ArrayList<Integer>();
static boolean judge(int st) {
int s = st & (st >> 1), t = st & (st >> 2);
//右移一位
return (s | t) == 0;
}
static int count(int st) {
int res = 0;
for (int i = 0; i < m; i++) {
res += (1 & st >> i);
}
return res;
}
static int n, m, N = 105;
static int[] g = new int[N];
}
package 状态压缩dp;
import java.util.Scanner;
/**
* 农夫约翰的土地由M*N个小方格组成,现在他要在土地里种植玉米。
* 非常遗憾,部分土地是不育的,无法种植。
* 而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
* 现在给定土地的大小,请你求出共有多少种种植方法。
* 土地上什么都不种也算一种方法。
* 输入格式
* 第1行包含两个整数M和N。
* 第2..M+1行:每行包含N个整数0或1,用来描述整个土地的状况,1表示该块土地肥沃,0表示该块土地不育。
* 输出格式
* 输出总种植方法对100000000取模后的值。
* 数据范围
* 1≤M,N≤12
* 输入样例:
* 2 3
* 1 1 1
* 0 1 0
* 输出样例:
* 9
* 本题要求种植玉米的方案数,没有种多少的限制。
* 状态表示f[i][j]表示种了前i行玉米田且第i行的状态为j的方案数。
* 题目给出了两个限制,第一个是相邻的土地不能同时种玉米,
* 可以翻译为同一行不存在相邻的两个1,即状态st & (st >> 1)为0;
* 并且相邻两行相同的列不能同时为1,即对第i-1行的状态s和第i行的状态t有s & t为0.
* 第二个限制是不育的土地不能种植,可以将各行不育的土地坐标读入向量,
* 然后在枚举状态时判断下是否合法即可,更方便的办法是:
* <p>
* for(int i = 1;i <= m;i++){
* for(int j = n - 1;j >= 0;j--){
* cin>>x;
* if(!x) a[i] += 1 << j;
* }
* }
* <p>
* 将一行中不肥沃的土地同样存储为一个状态,比如第一块和第四块不肥沃,
* 就可以存储为1001,下次在枚举状态时,直接与不合法的状态与一下,
* 结果不为0就不合法。每一行的合法方案数都是从上一行的合法方案数中转移过来的,
* 所以状态转移方程为f[i][j] = f[i][j] + f[i-1][k],
* 其中k为上一行能够转化到第i行的合法状态。
*/
public class 种玉米 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
m = sc.nextInt();
n = sc.nextInt();
int x = 0;
//二进制存储地图
for (int i = 1; i <= m; i++) {//有m行
for (int j = n - 1; j >= 0; j--) {
//高位开始读取
x = sc.nextInt();
if (x == 0) a[i] += 1 << j;
}
}
//比如第一块和第四块不肥沃,就可以存储为1001
f[0][0] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 0; j < 1 << n; j++) {
if (judge(j) && (j & a[i]) == 0) {
for (int k = 0; k < 1 << n; k++) {
if (judge(k) && (k & a[i - 1]) == 0 && (j & k) == 0)
f[i][j] = f[i][j] + f[i - 1][k] % (int) 1e8;
}
}
}
}
int res = 0;
for (int i = 0; i < 1 << n; i++) {
res = (res + f[m][i] )% (int) 1e8;
}
System.out.println(res);
}
//如果没有相邻的两个一为true
static boolean judge(int n) {
return (n & (n >> 1)) == 0;
}
static int[] a = new int[15];
static int m, n;
static int[][] f = new int[15][1 << 15];
}
package 状态压缩dp;
import java.util.ArrayList;
import java.util.Scanner;
/**
* https://blog.csdn.net/qq_30277239/article/details/104202667
* 在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8个格子,求使它们无法互相攻击的方案总数。
* 输入格式
* 共一行,包含两个整数 n和 k。
* 输出格式
* 共一行,表示方案总数,若不能够放置则输出0。
* 数据范围
* 1≤n≤10,
* 0≤k≤n^2
* 输入样例:
* 3 2
* 输出样例:
* 16
* 本题考察状态压缩DP,而且是棋盘类状压。
* 首先,很容易想到用一串二进制数表示一行放置国王的情况,1表示放置,0表示未放置。
* 而且由每个国王可以攻击周围的八连通区域的格子推出,每一行的状态最多会影响下一行,
* 而不会影响下下行,这为我们逐行进行状态转移提供了可能性。首先,分析从第一行开始,
* 逐行放置国王,如何放置是合法的。第一个条件,第i行不能有相邻两列都放了国王,
* 设第i列的状态为st,只需要st & (st >> 1)为0就可以说明不存在相邻两列都是1了。
* 第二个条件,根据上一行的状态如何得出下一行哪些状态是合法的。
* 首先,第i-1行第j列放置了国王,则第i行第j列就不能放置了,即s & t要为0,
* 其中s为i-1行的状态,t为第i行的状态;另外,上下两行对角线也不能同时为1,
* 只需要判断s | t是否存在相邻的1即可,不存在相邻的1则说明对角线不同时为1。
* 状态表示:本来只需要第一维表示遍历到的行数,第二维表示第i行的状态即可,
* 但是由于放置的国王数不能超过k,所以需要增加一维表示已经放置的国王数。
* 故f[i][j][k]表示放完前i行的国王,一共放了j个国王且第i行状态为k的方案数。
* 状态转移方程为f[i][j][k] = f[i][j][k] + f[i-1][j-cnt[i]][k'],
* 其中cnt[i]表示第i行放的国王数量,k表示放置第i行前第i-1行的状态,不难理解这个状态的转移,
* 就像01背包问题中选择第i个物品相当于f[i-1][j-v] + w一样,f[i][j][k]这个状态只要合法,
* 方案数自然等于所有能从第i-1行的合法状态转移到f[i][j][k]状态的方案数之和。
* 本题并不复杂,只是稍微麻烦的是为了提高效率还需要预处理所有合法的状态,
* 将所有不存在相邻1的合法状态都存进向量a中,并统计出每个状态包含1的数量。
* 另外,a中的合法状态可以转移到哪些合法的状态,也可以预处理出来存进向量b中。
* 本题方案数可能很大,需要用long long存储。
*/
public class 骑士 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < 1 << n; i++) {
if (check(i)) {
a.add(i);//去除相邻的放置方法
cnt.add(count(i));
}
}
int s = 0, t = 0, u = 0;
for (int i = 0; i < a.size(); i++) {
for (int j = 0; j < a.size(); j++) {
s = a.get(i);
t = a.get(j);
if ((s & t) == 0 && check(s | t)) b[s].add(t);
//去除s&t(i-1行&i)两行之内有相同元素
//check(s|t)把有1的全部置为1,如果有相邻1说明对角线为1
//预处理所有方案
}
}
f[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {//枚举个数
for (int k = 0; k < a.size(); k++) {
t = a.get(k);//取出作为第i行
for (s = 0; s < b[t].size(); s++) {
u = b[t].get(s); //枚举与t不冲突的u
if (j >= cnt.get(k))//第i行的骑士个数要合法
f[i][j][t] += f[i - 1][j - cnt.get(k)][u];
}
}
}
}
long res = 0;
for (int i = 0; i < a.size(); i++) {
res += f[n][m][a.get(i)];
}
System.out.println(res);
}
static int n, m;
static long[][][] f = new long[12][105][1 << 12];
static ArrayList<Integer>[] b = new ArrayList[1 << 12];
static {
for (int i = 0; i < b.length; i++) {
b[i] = new ArrayList<Integer>();
}
}
static ArrayList<Integer> a = new ArrayList<Integer>();
static ArrayList<Integer> cnt = new ArrayList<Integer>();
static boolean check(int st) {
return (st & (st >> 1)) == 0;
//判断相邻两列是否冲突,同时为1
}
//计算该行有多少个1
static int count(int n) {
int res = 0;
while (n != 0) {
if ((n & 1) == 1) res++;
n >>= 1;
}
return res;
}
}
package 状态机模型;
import java.util.Arrays;
import java.util.Scanner;
/**
* 阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
* 这条街上一共有 N家店铺,每家店中都有一些现金。
* 阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
* 作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。
* 他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
* 输入的第一行是一个整数 T,表示一共有 T组数据。
* 接下来的每组数据,第一行是一个整数 N,表示一共有 N家店铺。
* 第二行是 N个被空格分开的正整数,表示每一家店铺中的现金数量。
* 每家店铺中的现金数量均不超过1000。
* 输出格式
* 对于每组数据,输出一行。
* 该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
* 数据范围
* 1≤T≤50,
* 1≤N≤10^5
* 输入样例:
* 2
* 3
* 1 8 2
* 4
* 10 7 6 14
* 输出样例:
* 8
* 24
* 样例解释
* 对于第一组样例,阿福选择第2家店铺行窃,获得的现金数量为8。
* 对于第二组样例,阿福选择第1和4家店铺行窃,获得的现金数量为10+14=24。
* 分析:
* 方法一:线性DP
* 作为一个典型的线性dp问题,本题可以用线性DP的解法来解决。
* 状态表示f[i]表示在前i家店铺中能够获得的最多的现金。
* 如果第i家店铺不洗劫,则获得的现金与在前i-1家店铺获得的最大现金一致,
* 即f[i] = f[i-1];如果第i家店铺洗劫,则第i-1家店铺不能洗劫,
* 否则会触发报警器,故此时f[i] = f[i-2] + w,w表示洗劫第i家店铺可以获得的现金。
* 故状态转移方程为f[i] = max(f[i-1],f[i-2]+w),i >= 2。
* 这里的边界状态为f[0] = 0,f[1] = w1,因为只有一家商铺时选择洗劫必然是最优选择。
* 状态机模型:
* 有限状态机(Finite-state machine)又称有限状态自动机,
* 是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
* 在本题中,当我们从前往后遍历到第i个商店时,用0表示不洗劫该商店,1表示洗劫该商店。
* 则当前状态为洗劫了第i个商店时,状态为1,下一个商店只能不洗劫,
* 即状态1只能转移到状态0;当前状态为0,即没有洗劫第i个商店时,
* 下一个状态可以不洗劫也可以洗劫,即状态0既可以转移到状态0,也可以转移到状态1,
* 本题的状态机如上图所示。
* 设f[i]0]表示在前i家店铺中不洗劫第i家店铺,
* f[i][1]表示洗劫第i家店铺,
* 状态转移方程为f[i][1] = f[i-1][0] + w,f[i][0] = max(f[i-1][0],f[i-1][1]),
* 最后要求的最大现金等于max(f[n][0],f[n][1])
* <p>
* 相当于0 1两个点的图,三条边转移方式
* 0->1
* 0->0
* 1->0
* 令f[i][0]代表不抢第i家店铺
*/
public class 大盗阿福 {
public static void main(String[] args) {
fin();
}
//线性dp
static void lindp() {
Scanner sc = new Scanner(System.in);
t = sc.nextInt();
while (t-- != 0) {
n = sc.nextInt();
Arrays.fill(dp, 0);
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
dp[1] = a[0];
for (int i = 2; i <= n; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + a[i - 1]);
}
System.out.println(dp[n]);
}
}
//状态机
static void fin() {
Scanner sc = new Scanner(System.in);
t = sc.nextInt();
while (t-- != 0) {
n = sc.nextInt();
Arrays.fill(dp, 0);
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
}
//从1开始没有边界问题
for (int i = 1; i <= n; i++) {
//考虑last
//从上一个也不选,或者上一个选转移过来
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + a[i];
//从上一个不选转移过来
}
System.out.println(Math.max(f[n][0], f[n][1]));
}
}
static int[][] f = new int[100050][2];
static int[] dp = new int[100050];
static int[] a = new int[100050];
static int t, n;
}
package 状态机模型;
import java.util.Scanner;
/**
* https://blog.csdn.net/qq_30277239/article/details/104159301
* 给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i天的价格。
* 设计一个算法来计算你所能获取的最大利润,你最多可以完成 k笔交易。
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。一次买入卖出合为一笔交易。
* 输入格式
* 第一行包含整数 N和 k,表示数组的长度以及你可以完成的最大交易数量。
* 第二行包含 N个不超过 10000的正整数,表示完整的数组。
* 输出格式
* 输出一个整数,表示最大利润。
* 数据范围
* 1≤N≤10^5,
* 1≤k≤100
* 输入样例1:
* 3 2
* 2 4 1
* 输出样例1:
* 2
* 输入样例2:
* 6 2
* 3 2 6 5 0 3
* 输出样例2:
* 7
* 样例解释
* 样例1:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
* 样例2:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
* 随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。共计利润 4+3 = 7.
* 分析:
* 本题要求交易不超过k次交易赚取的最大利润,一次买卖为一次交易。
* 根据上图的状态机,如果当前状态持有股票,到下一天可以选择继续持有,
* 也可以选择卖出;如果当前状态未持仓,到下一天可以继续不持仓也可以买进。
* 由于有k次交易的限制,状态表示时需要加入一维表示已经交易的次数,
* 这里买入一次视为开始了一次交易。f[i][j][0]表示到第i天已经进行了j次交易且此时未持仓,
* f[i][j][1]表示到第i天已经进行了j次交易且此时持有仓位。
* 首先看要到达f[i][j][0]的状态前一天的状态可以是0或者1,如果前一天的状态是0,
* 说明前一天未持仓且已经进行了j次交易,即f[i][j][0] = f[i-1][j][0],
* 如果前一天有持仓,说明第i天卖出了股票,
* 第i-1天的状态是f[i-1][j][1],f[i][j][0] = f[i-1][j][1] + w[i],
* 加上w是因为卖出到账w元,故f[i][j][0] = max(f[i-1][j][0],f[i-1][j][1]+w[i])。
* 同理,要到达状态f[i][j][1],若前一天状态是0,则第i天买入了股票,进行了第j次交易,
* 前一天只进行了j-1次交易,即f[i][j][1] = f[i-1][j-1][0] - w[i],
* 减去w表示买入股票扣了w元;若前一天的状态是1,则说明第i天未进行操作,
* f[i][j][1] = f[i-1][j][1],故f[i][j][1] = max(f[i-1][j][1],f[i-1][j-1][0]-w[i])。
* 从而状态转移方程就求出来了,下面考虑边界状态,f[i][0][0]表示第i天都未进行交易,
* 收益是0,除此之外,f[i][[j][0]与f[i][0][1]的初始状态都应该是不合法的,设置为-INF。
*/
public class 股票买卖4 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
}
o2();
O3();
}
static void O3() {
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= k; j++) {
for (int l = 0; l < 2; l++) {
f[i][j][l] = Integer.MIN_VALUE / 2;
}
}
}
f[0][0][0] = 0;
for (int i = 1; i <= n; i++) {
f[i][0][0] = 0;
for (int j = 1; j <= k; j++) {
f[i][j][0] = Math.max(f[i - 1][j][0], f[i - 1][j][1] + a[i]);
//卖出不消耗次数
f[i][j][1] = Math.max(f[i - 1][j][1], f[i - 1][j - 1][0] - a[i]);
//买入消耗次数
}
}
int res = 0;
for (int i = 0; i <= k; i++) {
res = Math.max(res, f[n][i][0]);
}
System.out.println(res);
}
static void o2() {
for (int j = 0; j <= k; j++) {
for (int l = 0; l < 2; l++) {
dp[j][l] = Integer.MIN_VALUE / 2;
}
}
dp[0][0] = 0;
int res = 0;
for (int i = 1; i <= n; i++) {
for (int j = k; j >= 1; j--) {
dp[j][0] = Math.max(dp[j][0], dp[j][1] + a[i]);
dp[j][1] = Math.max(dp[j][1], dp[j - 1][0] - a[i]);
}
}
for (int i = 0; i <= k; i++) {
res = Math.max(res, dp[i][0]);
}
System.out.println(res);
}
static int[] a = new int[100005];
static int n, k, N = 100005, M = 105;
static int[][][] f = new int[N][M][2];
static int[][] dp = new int[M][2];
}
package 状态机模型;
import java.util.Scanner;
/**
* 给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i天的价格。
* 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
* 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
* 卖出股票后,你无法在第二天买入股票 (即冷冻期为1天)。
* 输入格式
* 第一行包含整数 N,表示数组长度。
* 第二行包含 N个不超过 10000的正整数,表示完整的数组。
* 输出格式
* 输出一个整数,表示最大利润。
* 数据范围
* 1≤N≤10^5
* 输入样例:
* 5
* 1 2 3 0 2
* 输出样例:
* 3
* 样例解释
* 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出],
* 第一笔交易可得利润 2-1 = 1,第二笔交易可得利润 2-0 = 2,共得利润 1+2 = 3。
*/
public class 股票买卖5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
}
time();
}
/**
* 线性dp
*/
static void linear() {
f[1][1] = -a[1];
//第一天买入,就是-a[1],同理f[1][0]=0
for (int i = 2; i <= n; i++) {
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1] + a[i]);
f[i][1] = Math.max(f[i - 1][1], f[i - 2][0] - a[i]);
}
System.out.println(f[n][0]);
}
/**
* 状态机模型
* https://blog.csdn.net/qq_30277239/article/details/104161014
*/
static void time() {
dp[0][0] = dp[0][1] = (int) -1e9;//非法方案
for (int i = 1; i <= n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - a[i]);
dp[i][1] = dp[i - 1][0] + a[i];
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1]);
}
System.out.println(Math.max(dp[n][1], dp[n][2]));
}
static int[][] dp = new int[(int) (1e5 + 10)][3];
static int[][] f = new int[(int) 1e5][2];
static int[] a = new int[(int) (1e5 + 10)];
static int n;
}
......@@ -19,16 +19,74 @@ import java.util.Scanner;
* 1 -3 5 1 -2 3
* 输出样例:
* 7
* 有长度要求,需要求前缀和
* 状态表示f[i]表示以第i个数字结尾的长度不超过m的子序列的最大和。
* s[i]表示数组的前缀和,则a[l] + ... + a[r] = s[r] - s[l-1]。
* 状态转移方程为f[i] = max(s[i] - s[j-1]),
* 其中i - j大于等于0并且不超过m,因此不难得出以下的DP代码:
*/
public class 最大子序列和 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 1; i <= n; i++) {
s[i] += sc.nextInt() + s[i - 1];
}
System.out.println(d());
}
/**
* 枚举i~j区间,因为区间长度不超过m,O(n^2)
*
* @return 区间长度不超过m的最大值
*/
static int on2() {
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = i; j >= 1; j--) {
if (i - j >= m) continue;
//区间的话一共有i-j+1个值,同理可以写成i-j+1>m
//则显然i-j>=m
f[i] = Math.max(f[i], s[i] - s[j - 1]);
//求以f[i]结尾长度为1~m的区间的最大值
}
ans = Math.max(f[i], ans);
}
return ans;
}
/**
* 本题的数据范围是30w,上面平方级别复杂度的代码显然会超时。
* 如果单纯的看这题,不去考虑DP的思想,
* 无非是求以第i个数字结尾的长度不超过m的子序列的和,
* 很容易想到单调队列去实现。如果按照DP的思想,也是可以推出使用单调队列优化的。
* 我们在DP过程中求f[i]时,f[i]的值是s[i] - s[i-1],s[i] - s[i-2],...,s[i] - s[i-m]这m个数中的最大值,
* 当然,当i小于m时,只用枚举到s[i] - s[0],所以就是求滑动窗口大小是m的最大值了,正是单调队列的经典应用。
* 在单调队列的实现时,需要先在队列中加入哨兵结点s[0],
* 后面就是解决四个问题了。第一,何时出队头,枚举到第i个数字时,
* 第i个数字还没加入队列时,队头元素的下标允许的最小值是i - m,
* 所以当i - q[hh] > m时就需要出队头了;第二,何时出队尾,
* 我们需要队头的元素是s[q[hh]]最小的元素,所以当s[i] <= s[q[tt]]时,
* 出队尾元素;第三,何时加入新元素,队尾元素该出的出完了就可以将i加入到队列中了;
* 第四,何时更新我们要求的长度不超过m的子序列的和的最大值res,
* 只要在确保队列中的元素个数不超过m时就可以尝试更新res了。
* 使用单调队列优化DP的方法时间复杂度是O(n)。
*/
static int d() {
int ans = -19;
int hh = 0, tt = 0;
for (int i = 1; i <= n; i++) {
if (i - q[hh] > m) hh++;
ans = Math.max(ans, s[i] - s[q[hh]]);
while (hh <= tt && s[q[tt]] >= s[i]) tt--;
q[++tt] = i;
}
return ans;
}
static int n, m;
static int[] f = new int[300010];
static int[] q = new int[300010];
static int[] s = new int[300010];
}
package 线性dp;
import java.util.Scanner;
/**
* 给定n个长度不超过10的字符串以及m次询问,每次询问给出一个字符串和一个操作次数上限。
* 对于每次询问,请你求出给定的n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
......@@ -14,17 +16,17 @@ package 线性dp;
* 数据范围
* 1≤n,m≤1000,
* 输入样例:
* 3 2
* abc
* acd
* bcd
* ab 1
* acbd 2
* 3 2
* abc
* acd
* bcd
* ab 1
* acbd 2
* 输出样例:
* 1
* 3
* 1
* 3
* 分析:
* 本题可直接调用AcWing 902 最短编辑距离中的算法框架。
* 本题可直接调用AcWing 902最短编辑距离中的算法框架。
* 要求n个字符串中有多少个字符串可以在给定次数内转换成指定的m个字符串,
* 一共需要调用求最短编辑距离函数nm次,由于字符串长度不超过10,
* 故单次求最短编辑距离复杂度为100,
......@@ -32,6 +34,48 @@ package 线性dp;
*/
public class 编辑距离 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 0; i < n; i++) {
a[i] = sc.next().toCharArray();
}
int ans = 0, cnt;
char[] t;
while (m-- != 0) {
ans = 0;
t = sc.next().toCharArray();
cnt = sc.nextInt();
for (int i = 0; i < n; i++) {
if (ed(a[i], t) <= cnt) ans++;
}
//计算第i个能否在cnt步之内变成t
System.out.println(ans);
}
}
static int ed(char[] a, char[] b) {
int n = a.length, m = b.length;
for (int i = 0; i < n; i++) {
f[i][0] = i;
}
//边界,i个字符转换成0个字符,需要转换i次
for (int i = 0; i < m; i++) {
f[0][i] = i;
}
//边界,0个字符转换成i个字符,需要转换i次
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i - 1] == b[j - 1]) f[i][j] = f[i - 1][j - 1];
else f[i][j] = Math.min(Math.min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
}
}
return f[n][m];
}
static int n, m, N = 1005;
static int[] b = new int[N];
static int[][] f = new int[N][N];
static char[][] a = new char[N][10];
}
package 线性dp.背包模型;
package 背包模型;
/**
* 小明手里有n元钱,用来买书:价格为10元,20,50,100,
......
package 线性dp.背包模型;
package 背包模型;
/**
* 1分2分3分的硬币,把钱N换成零钱有多少种兑换方法,n<3w
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
/**
* 与完全背包不同的是每个物品最多选s[i]
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Arrays;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
/**
* https://blog.csdn.net/xiji333/article/details/104226993
* 2. 01背包问题
* 状态定义:集合所有只从前i个物品选,且总体积不超过j的所有选法的集合
* f[i,j]存的是最大价值
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.Scanner;
......
package 线性dp.背包模型;
package 背包模型;
import java.util.ArrayList;
import java.util.Scanner;
......
package 递归;
/**
* 给定n个数,取出m个数使得和为s,求方案数
*/
public class m任取n满足 {
public static void main(String[] args) {
f(0, 0, 7);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册