参考
【算法2-2】常见优化技巧 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
- P1102 A-B 数对
- P1638 逛画展
- P1115 最大子段和
- P7072 [CSP-J2020] 直播获奖
- P2671 [NOIP2015 普及组] 求和
- P4147 玉蟾宫
- P2866 [USACO06NOV] Bad Hair Day S
- P1950 长方形
- P2032 扫描
- P2216 [HAOI2007] 理想的正方形
- UVA11572 唯一的雪花 Unique Snowflakes
- P4653 [CEOI2017] Sure Bet
- P3143 [USACO16OPEN] Diamond Collector S
- P7910 [CSP-J 2021] 插入排序
- P1578 奶牛浴场
- P3467 [POI2008] PLA-Postering
- P1886 滑动窗口 /【模板】单调队列
- P2880 [USACO07JAN] Balanced Lineup G
- P1714 切蛋糕
- P1725 琪露诺
双指针
一般来说双指针可以优化原本需要O(n2)时间的遍历+求连续区间满足某一条件的这种问题,而且数据规模比较大
P1102 A-B 数对
1-6二分做过。
package _1_6;import java.io.*;
import java.util.Arrays;public class P1102 {private static StreamTokenizer in;private static PrintWriter out;private static long[] nums;private static long c;private static int n;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));out = new PrintWriter(System.out);in.nextToken();n = (int) in.nval;in.nextToken();c = (long) in.nval;nums = new long[n];for (int i = 0; i < n; i++) {in.nextToken();nums[i] = (long) in.nval;}// 先排序Arrays.sort(nums);// algo 双指针long count = 0;int l = 0, r = 0;for (int i = 0; i < n; i++) {// 找到第一个nums[l]-nums[i]>=c,nums[r]-nums[i]>c,则l~r-1区间就是-nums[i]==cwhile (l < n && nums[l] - nums[i] < c) l++;while (r < n && nums[r] - nums[i] <= c) r++;// 如果l==n则表示找不到对应值if (l < n && nums[l] - nums[i] == c && r >= 1 && nums[r - 1] - nums[i] == c)count += r - l;}out.println(count);out.flush();out.close();}
}
P1638 逛画展
看着n的数量级,只能O(n)时间才AC,可以用双指针
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;public class P1638 {private static StreamTokenizer in;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));int n = nextInt();int m = nextInt();int[] draws = new int[n + 1];// visited存当前范围包含了画师i的次数int[] visited = new int[m + 1];// cnt表示包含了画师的种数int cnt = 0;for (int i = 1; i <= n; i++) {draws[i] = nextInt();}// algo 双指针int l, r;l = 1;r = l - 1;// r右移一直到包含所有画师while (cnt != m && r <= n - 1) {r++;// 如果当前这幅画的画师没有被包含,那就加入if (visited[draws[r]] == 0) {cnt++;}visited[draws[r]] += 1;}// 去除左端重复的画师while (r - l + 1 >= m && visited[draws[l]] > 1) {visited[draws[l]]--;l++;}if (r - l + 1 == m) {System.out.printf("%d %d", l, r);return;}// 此时lr区间已经满足了条件包含所有画师的目前最短,但是也有可能后续还有更短的区间// 继续r+1,每次去除左端重复,如果区间更小了那就更新(如果相同不更新,因为多组解得l更小的)int minl = l, minr = r;while (r <= n - 1) {r++;visited[draws[r]] += 1;// 去除左端重复的画师while (r - l + 1 >= m && visited[draws[l]] > 1) {visited[draws[l]]--;l++;}// 更新更短的区间if (r - l < minr - minl) {minr = r;minl = l;}}System.out.printf("%d %d", minl, minr);}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
空间换时间
(题外话:之前不是有一道异或题吗,4Mb的空间Java根本AC不了)
P7072 [CSP-J2020] 直播获奖
首先想到的就是每次读入选手的成绩后排序,然后求出前w%个人的最低成绩。
Java的Arrays.sort用了一个TimSort,时间复杂度是O(ologn)。不过注释说如有部分排序了,那这个算法的实际时间远低于nlgn。
不过这样的话,这个朴素的写法的时间复杂度是O(n2logn),可以过104的数量级,但是后面三个105就TLE。
这里有个注意的点,Arrays.sort(T[],start,end,Comparator)这里的数组要用包装类的
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
import java.util.Comparator;public class P7072 {private static StreamTokenizer in;private static PrintWriter out;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));out = new PrintWriter(System.out);int n = nextInt();double w = nextInt() * 0.01;Integer[] scores = new Integer[n + 1];for (int i = 1; i <= n; i++) {// 当前i个人,前w%就是i*0.01*w个人,然后再获得最后一个人的成绩scores[i] = nextInt();// 降序排序,这里要用Integer包装类,所以也可以选择从后面往前面选Arrays.sort(scores, 1, i + 1, Comparator.reverseOrder());int t = (int) (i * w);if (t < 1) {t = 1;}out.printf("%d ", scores[t]);}out.flush();out.close();}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
关键点在排序时间上,可以使用时间复杂度为O(n)的桶排序:点题用空间换时间,1-2曾做到过一道桶排序。因为桶排序不是比较排序,所以不受O(ologn)的限制(比较排序这个时间复杂度是最优的了)。还有一点,题中的值范围<=600,所以空间也不用开很大,也可以想到桶排序。
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;
import java.util.Comparator;public class P7072 {private static StreamTokenizer in;private static PrintWriter out;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));out = new PrintWriter(System.out);int n = nextInt();double w = nextInt() * 0.01;int[] scores = new int[605];for (int i = 1; i <= n; i++) {scores[nextInt()]++;// 当前i个人,前w%就是i*0.01*w个人,然后再获得最后一个人的成绩int t = (int) (i * w);if (t < 1) {t = 1;}// 找第t个人的分数(注意降序)int j;for (j = 604; j >= 0; j--) {if (scores[j] > 0) {// 这里会对scores操作,所以一次性-去,这样就不会改变scores了,反正判断也是t<=0t -= scores[j];}// 因为可能重分,所以t<=0的时候也算找到分数线if (t <= 0) {out.printf("%d ", j);break;}}}out.flush();out.close();}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
P2671 [NOIP2015 普及组] 求和
按照颜色分组想到了,把等式移相一下没想到。
单调栈
P2866 [USACO06NOV] Bad Hair Day S
什么是单调栈?单调栈(C/C++)-CSDN博客
就是栈里维护一个单调序列:可解题型如下,往一边找第一个比自身大/小的值
以这道题为例,都是往右边找第一个>=自身(条件)的数,所以从后往前遍历。
- 如果栈空,直接保存结果-1;
- 如果栈顶元素满足条件(>=自身),直接保存结果为栈顶元素(此题要的是下标,比较的时候用的是下标对应值);
- 如果栈顶元素不满足条件(<自身),则出栈直到情况1或2。
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.LinkedList;public class P2866 {private static StreamTokenizer in;private static int n;private static int[] hs;private static LinkedList<Integer> stack;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));n = nextInt();hs = new int[n + 1];stack = new LinkedList<Integer>();for (int i = 1; i <= n; i++) {hs[i] = nextInt();}long cnt = 0;// 注意从后往前遍历for (int i = n; i >= 0; i--) {int t = findHeigher(i);if (t == -1) {cnt += n - i;} else {cnt += t - i - 1;}}System.out.println(cnt);}// algo 单调栈public static int findHeigher(int i) {// 弹出栈顶,直到满足情况1或2,返回结果(注意栈里存的是下标),压入当前值while (stack.size() > 0 && hs[stack.getFirst()] < hs[i]) {stack.removeFirst();}// 栈空,返回结果-1,压入当前值if (stack.size() == 0) {stack.addFirst(i);return -1;} else {// 这里要先获取栈顶元素,再压栈int t = stack.getFirst();stack.addFirst(i);return t;}}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
单调队列
P1886 滑动窗口 /【模板】单调队列
什么是单调队列?单调队列 - Lan_Sky - 博客园 (cnblogs.com)、[数据结构]–单调队列-CSDN博客
单调队列通常解决动态小区间中寻找极值问题。
- 动态区间找最小值,使用单调递增序列,队首就是最小值;
- 动态区间找最大值,使用单调递减序列,队首就是最大值;
中间操作都是类似的,例如找最小值,要维护单调递增序列
如果当前遍历到的值违反了队列的单调要求,那么就要把元素从队尾出队,每次都要把新元素从队尾入队。
如果当前队列中的元素的下标已经不在滑动窗口内了,则这个值从队首出队。
为什么最小值是维护单调递增序列,可以见2。
- 因为窗口在滑动,所以即使目前这个值不是最小的,但是窗口滑动之后他有可能成为最小的(因为他被放在了极小的后面,窗口又向右边滑动),所以要存起来。
- 但是如果当前这个值比队列一些元素都小,放入队列后即使找最小值也已经不可能再找队首那部分的值了(都有更小的了),所以前面的要出队。
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.LinkedList;public class P1886 {private static StreamTokenizer in;private static PrintWriter out;private static LinkedList<Integer> queue;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));out = new PrintWriter(System.out);int n = nextInt();int k = nextInt();int[] num = new int[n + 1];for (int i = 1; i <= n; i++) {num[i] = nextInt();}// algo 单调队列queue = new LinkedList<Integer>();// 求最小值,维护递增队列for (int i = 1; i <= n; i++) {// 把队首部分,不在滑动窗口内的值去掉,这里i属于窗口右边界:窗口部分i-k+1~iwhile (queue.size() > 0 && queue.getFirst() < (i - k + 1)) {queue.removeFirst();}// 从队尾开始维护队列单调递增while (queue.size() > 0 && num[queue.getLast()] > num[i]) {queue.removeLast();}// 入队queue.add(i);// 输出当前最值,注意右边界i从k开始才算一个完整窗口if (i >= k) {out.printf("%d ", num[queue.getFirst()]);}}out.println();queue.clear();// 求最大值,维护递减队列for (int i = 1; i <= n; i++) {// 把队首部分,不在滑动窗口内的值去掉,这里i属于窗口右边界:窗口部分i-k+1~iwhile (queue.size() > 0 && queue.getFirst() < (i - k + 1)) {queue.removeFirst();}// 从队尾开始维护队列单调递减while (queue.size() > 0 && num[queue.getLast()] < num[i]) {queue.removeLast();}// 入队queue.add(i);// 输出当前最值,注意右边界i从k开始才算一个完整窗口if (i >= k) {out.printf("%d ", num[queue.getFirst()]);}}out.flush();out.close();}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
60%AC,有几个TLE,那换静态数组?
package _2_2;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.LinkedList;public class P1886 {private static StreamTokenizer in;private static PrintWriter out;private static LinkedList<Integer> queue;private static int n;private static int k;private static int[] nums;public static void main(String[] args) throws IOException {in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));out = new PrintWriter(System.out);n = nextInt();k = nextInt();nums = new int[n + 1];for (int i = 1; i <= n; i++) {nums[i] = nextInt();}fun2();out.flush();out.close();}// algo 单调队列:静态数组版public static void fun2() {int[] queue = new int[n + 1];// 双指针指向队首尾,此时size=tail-head+1,所以表示空即tail+1=headint head = 1, tail = 0;// 求最小值,维护递增队列for (int i = 1; i <= n; i++) {// 把队首部分,不在滑动窗口内的值去掉,这里i属于窗口右边界:窗口部分i-k+1~iwhile (tail - head + 1 > 0 && queue[head] < (i - k + 1)) {head++;}// 从队尾开始维护队列单调递增while (tail - head + 1 > 0 && nums[queue[tail]] > nums[i]) {tail--;}// 入队queue[++tail] = i;// 输出当前最值,注意右边界i从k开始才算一个完整窗口if (i >= k) {out.printf("%d ", nums[queue[head]]);}}out.println();queue = new int[n + 1];head = 1;tail = 0;// 求最大值,维护递减队列for (int i = 1; i <= n; i++) {// 把队首部分,不在滑动窗口内的值去掉,这里i属于窗口右边界:窗口部分i-k+1~iwhile (tail - head + 1 > 0 && queue[head] < (i - k + 1)) {head++;}// 从队尾开始维护队列单调递增while (tail - head + 1 > 0 && nums[queue[tail]] < nums[i]) {tail--;}// 入队queue[++tail] = i;// 输出当前最值,注意右边界i从k开始才算一个完整窗口if (i >= k) {out.printf("%d ", nums[queue[head]]);}}}public static int nextInt() throws IOException {in.nextToken();return (int) in.nval;}
}
就多了一个AC
区间最值
P4147 玉蟾宫
P1950 长方形
P2216 [HAOI2007] 理想的正方形
都是提高题
浅谈用极大化思想解决最大子矩形问题-CSDN博客