目录
例题一:Codeforces Round 466 (Div. 2) Cashback
例题一:Codeforces Round 466 (Div. 2) Cashback
题目大意:
思路解析:
如果c==1,那么无论如何 答案都为0.
如果c!=1,我们考虑如果最优答案有一段区间长度小于c,那么它对答案的贡献值,等于将这个区间划分为长度为1的多个小段,
如果有一段区间长度大于c小于2 * c,那么相当于在里面找到需要删除的数在其旁划分出一个长度为c的区间,其余部分都划分长度为1的小区间。
如果有区间长度等于2*c,那么把c划分为两个长度为c的区间,只能一个区间删除一个对于这个区间长度为2*c才是等价的(其余情况,划分为单个长度为c的区间对于整体答案更加优秀)。说明无论如何将整体划分长度为1或者长度为n的区间是最优选择。
代码实现:
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
int n = input.nextInt();
int c = input.nextInt();
int[] arr = new int[n + 1];
int[] min = new int[n + 1];
long[] dp = new long[n + 1];
long[] sum = new long[n + 1];
for (int i = 1; i <= n; i++) {
arr[i] = input.nextInt();
sum[i] = sum[i - 1] + arr[i];
}
LinkedList<Integer> list = new LinkedList<>();
for (int i = 1; i <= Math.min(c, n); i++) {
if (list.isEmpty()){
list.add(i);
}else{
int j = list.getLast();
if(arr[j] <= arr[i]){
list.add(i);
}else{
while (arr[list.getLast()] > arr[i]){
list.removeLast();
if (list.isEmpty()) break;
}
list.add(i);
}
}
min[i] = arr[list.getFirst()];
}
for (int i = c + 1; i <= n; i++) {
while (!list.isEmpty() && list.getFirst() <= i - c) list.remove();
if (list.isEmpty()){
list.add(i);
}else{
int j = list.getLast();
if(arr[j] <= arr[i]){
list.add(i);
}else{
while (arr[list.getLast()] > arr[i]){
list.removeLast();
if (list.isEmpty()) break;
}
list.add(i);
}
}
min[i] = arr[list.getFirst()];
}
for (int i = 1; i <= n; i++) {
if (i >= c)
dp[i] = Math.min(dp[i - 1] + arr[i], dp[i - c] + sum[i] - sum[i - c] - min[i]);
else dp[i] = dp[i - 1] + arr[i];
}
out.println(dp[n]);
out.flush();
out.close();
br.close();
}
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static Input input = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}
例题二:poj2373 dividing the path
题目大意:
现在有一个长度为L的牧场,有N头牛,每个牛有一个活动范围(s,e) (为开区间),牛的活动范围可以重叠。现在有一个喷头,它喷射范围为【a,b】,需要使用这种喷头给牧场所有区域进行喷水,要求喷射范围不能超过牧场的边界。每个牛的活动范围只能被一个喷头喷射,且每个喷头的喷射范围不能重叠,求使得满足这些要求最少需要几个喷头。
思路解析:
因为喷射半径为整数且一个区域不能重复喷射,那么直径一定为偶数,即只能满足偶数的区间。
用dp[i]表示当前已经喷射了1-i的区域最少需要使用几个喷头,那么如果 i 落在牛的活动范围,这就是违法状态,应该赋inf。如何线性的判断,i是否落在牛的活动范围就是一个关键。
假设牛的活动范围 (2,5) (3,7)
那么让 sum[3]++,sum[5]--,sum[4]++,sum[7]--,再做一个前缀和,其中前缀和大于0的位置位于牛的活动范围内。
dp[i] = max(dp[j] + 1) i - 2 *b<= j<=i - 2*a,满足单调性,可以使用单调队列维护当前定长区间的最小值。
代码实现:
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
int n = input.nextInt();
int l = input.nextInt();
int a = input.nextInt();
int b = input.nextInt();
int inf = 1 << 31;
int[] cowTree = new int[l + 1];
for (int i = 0; i < n; i++) {
int x= input.nextInt();
int y = input.nextInt();
cowTree[x + 1]++;
cowTree[y]--;
}
int[] dp = new int[l + 1];
int sum = 0;
for (int i = 0; i <= l; i++) {
dp[i] = inf;
sum += cowTree[i];
cowTree[i] = sum > 0 ? 1 : 0;
}
a <<= 1;
b <<= 1;
LinkedList<Integer> list = new LinkedList<>();
for (int i = a; i <= b; i++) {
if (cowTree[i] == 0){
dp[i] = 1;
if (i <= b + 2 - a){
list.add(i);
}
}
}
for (int i = b + 2; i <= l; i+=2) {
if (cowTree[i] == 0){
while (!list.isEmpty()){
if (list.getFirst() < i - b) list.remove();
else break;
}
if(!list.isEmpty())
dp[i] = dp[list.getFirst()] + 1;
}
if (dp[i - a + 2] != inf) list.add(i - a + 2);
}
if (dp[l] == inf) out.println(-1);
else out.println(dp[l]);
out.flush();
out.close();
br.close();
}
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static Input input = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}
例题三:poj3017 cut the sequence
题目大意:
给定一个长度为n序列,求一种分割方式,使得每一个部分的和都满足不大于m的情况下求每个部分的最大值的和最小。
0<=N<=100000
A[i] >=0, M <= 4^18
思路解析:
dp[i]表示前i个序列,分割后每个部分的最大值的和最小是多少。
如果下标 j, j+1,j+2......i这多个数中最大数下标为k,这多个数之和小于m,那么其实可以发现dp[j-1]+arr[k]<=dp[j]+arr[k]<=dp[j+1]+arr[k],这是一定成立的条件。那么我们应该优先选择最小区间来进行合并答案。这又要求区间的最大值,又可以实现单调队列来实现,但是队首的最大值不一定是最优秀的划分答案,因为可以选择将队首这个最大值划分到上一个区间,所以要经历一个遍历的过程。
代码实现:
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
int n = input.nextInt();
long m = input.nextLong();
long[] arr = new long[n + 1];
boolean flag = false;
for (int i = 1; i <= n; i++) {
arr[i] = input.nextLong();
if (arr[i] > m) flag = true;
}
if (flag) {
out.println(-1);
} else {
int p = 1;
long sum = 0;
long[] dp = new long[n + 1];
int[] q = new int[n + 1]; // 用数组实现单调队列,方便dp时循环。
int l = 0; // [l,r)代表当前单调队列数组有效范围
int r = 0;
q[r++] = 0;
dp[1] = arr[1]; // dp[i]表示前i个数经分割后,每个部分最大值的和最小是多少。
for (int i = 1; i <= n; i++) {
sum += arr[i];
while (sum > m) sum -= arr[p++];
// 实现单调队列
while (l < r && q[l] < p) l++;
while (l < r && arr[q[r - 1]] <= arr[i]) r--;
q[r++] = i;
dp[i] = dp[p - 1] + arr[q[l]];
for (int j = l; j < r - 1; j++) {
dp[i] = Math.min(dp[i], dp[q[l]] + arr[q[l + 1]]);
}
}
out.println(dp[n]);
}
out.flush();
out.close();
br.close();
}
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static Input input = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}
例题四:瑰丽华尔兹
思路解析:
每一个时间段,它只会往一个方向滑动,所以我们单独考虑每一个时间的滑行情况,这样就只需要对一个方向进行统计。
dp[i][j][k] 表示当前第k个时间段已经滑动了地图的(i,j)处最多能滑动多少时间。
假如当前向下滑动,dp[i][j][k] = max(dp[i'][j][k-1] + i - i') 等价于 max(dp[i'][j][k-1] - i') + i。那么又是定长区间求最大值,又可以使用单调队列进行优化。 // 可以利用就地滚动的特点来优化dp的空间消耗。
代码实现:
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class Main {
static int[] dx = {0, -1, 1, 0, 0};
static int[] dy = {0, 0, 0, -1, 1};
static int ans = 0;
static int[] q = new int[205];
static int[] p = new int[205];
static int N;
static int M;
static int[][] map;
static int[][] dp;
public static void main(String[] args) throws IOException {
N = input.nextInt();
M = input.nextInt();
int x = input.nextInt();
int y = input.nextInt();
int k = input.nextInt();
int inf = Integer.MIN_VALUE;
map = new int[N + 1][M + 1];
for (int i = 0; i < N; i++) {
String str = input.next();
char[] s = str.toCharArray();
for (int j = 0; j < s.length; j++) {
if (s[j] == 'x') map[i + 1][j + 1] = 1; // 表示当前地方有家具
}
}
dp = new int[N + 1][M + 1];
for (int i = 0; i <= N; i++) {
Arrays.fill(dp[i], inf);
}
dp[x][y] = 0;
for (int l = 1; l <= k; l++) {
int st = input.nextInt();
int et = input.nextInt();
int d = input.nextInt();
int len = et - st + 1; // 当前时间段最多能滑动多远
if (d == 1) { // 向上滑动
for (int i = 1; i <= M; i++) {
work(N, i, len, d);
}
} else if (d == 2) { // 向下滑动
for (int i = 1; i <= M; i++) {
work(1, i, len, d);
}
} else if (d == 3) { // 向左滑动
for (int i = 1; i <= N; i++) {
work(i, M, len, d);
}
} else if (d == 4) { // 向右滑动
for (int i = 1; i <= N; i++) {
work(i, 1, len, d);
}
}
}
out.println(ans);
out.flush();
out.close();
br.close();
}
public static void work(int x, int y, int len, int d) {
int head = 1;
int tail = 0;
for (int i = 1; x >= 1 && x <= N && y >= 1 && y <= M; i++, x += dx[d], y += dy[d]) {
if (map[x][y] == 1) { // 如果当前位是家具,应该清空队列
head = 1;
tail = 0;
} else {
while (head <= tail && dp[x][y] >= q[tail] + i - p[tail]) tail--; // 保存队列的单调性
q[++tail] = dp[x][y]; // 保存改变之前的值,将其加入队列
p[tail] = i;
if (p[tail] - p[head] > len) head++;
dp[x][y] = q[head] + i - p[head]; // 用队列的最优值进行转移
ans = Math.max(dp[x][y], ans);
}
}
}
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static Input input = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}
例题5:多重背包使用单调队列优化实现
给定n种物品和一个背包,第i种物品的体积为ci,价值为wi,并且有mi个,背包的总容量为C,如何选择装入背包的物品,使背包装入的物品的总价值最大。
思路解析:
dp[i] = max(dp[j] + k*wi) 等价于 max(dp[i - k*c] + k*wi) 等价于 max(dp[b+y*c] - y*w) + m * w
b+m*c = i, 即假设用这个物品将背包剩余部分全部装完,然后枚举有y个物品不装,然后求这里面的最大值,这又可以变为单调队列来优化。
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, C;
int dp[N], q[N], num[N];
int w,c, m;
int main(){
cin >> n >> C;
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++){
cin >> m >> c >> w;
if (m > C / c) m = C/c;
for(int b = 0; b < c; b++){
int head = 1, tail = 1;
for(int y = 0; y <= (C - b) / c; y++){
int tmp = dp[b + y * c] - y*w;
while(head < tail && tmp >= q[tail - 1]) tail--;
q[tail] = tmp;
num[tail++]=y;
while (head < tail && y - num[head] > m) head++;
dp[b + y * c] = max(dp[b+ y*c], q[head] + y*w);
}
}
}
cout<<dp[C] <<endl;
}