2-17算法习题总结

贪心问题

[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 3 3 种果子,数目依次为 1 1 1 2 2 2 9 9 9 。可以先将 1 1 1 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。

输入格式

共两行。
第一行是一个整数 n ( 1 ≤ n ≤ 10000 ) n(1\leq n\leq 10000) n(1n10000) ,表示果子的种类数。

第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1ai20000) 是第 i i i 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231

样例 #1

样例输入 #1

3 
1 2 9

样例输出 #1

15

提示

对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n1000

对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n5000

对于全部的数据,保证有 n ≤ 10000 n \le 10000 n10000

代码如下
1.优先队列

package exercise.luogu.greedy;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

public class FruitMerging {

    // 定义一个简单的类来表示果子堆和其重量  
    static class FruitHeap {
        int weight;

        FruitHeap(int weight) {
            this.weight = weight;
        }
    }

    // 比较器,用于优先队列,使得重量最小的堆优先出队  
    static class FruitHeapComparator implements Comparator<FruitHeap> {
        public int compare(FruitHeap fh1, FruitHeap fh2) {
            return fh1.weight - fh2.weight;
        }
    }

    // 计算最小的体力耗费值  
    public static int minEnergy(int[] fruits) {
        PriorityQueue<FruitHeap> pq = new PriorityQueue<>(new FruitHeapComparator());

        // 将所有果子堆添加到优先队列中  
        for (int weight : fruits) {
            pq.add(new FruitHeap(weight));
        }

        int totalEnergy = 0;

        // 当队列中有多于一个堆时,持续合并  
        while (pq.size() > 1) {
            // 弹出两个最小的堆  
            FruitHeap fh1 = pq.poll();
            FruitHeap fh2 = pq.poll();

            // 合并它们,并计算合并的体力消耗  
            int combinedWeight = fh1.weight + fh2.weight;
            totalEnergy += combinedWeight;

            // 将合并后的堆添加回队列  
            pq.add(new FruitHeap(combinedWeight));
        }

        return totalEnergy;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] fruits = new int[n];
        for (int i = 0; i < fruits.length; i++) {
            fruits[i] = sc.nextInt();
        }
        System.out.println(minEnergy(fruits));
    }
}

2.普通队列模拟优先队列

package exercise.luogu.greedy;

import java.util.Arrays;
import java.util.Scanner;  
  
public class FruitMergingWithSortedArray {  
  
    // 使用插入排序对数组进行排序  
    public static int[] insertSort(int[] arr, int newValue) {
        int[] newArr = Arrays.copyOf(arr, arr.length + 1);  
        newArr[arr.length] = newValue;  
  
        for (int i = 1; i < newArr.length; i++) {  
            int key = newArr[i];  
            int j = i - 1;  
  
            // 将小于 key 的元素向右移动  
            while (j >= 0 && newArr[j] > key) {  
                newArr[j + 1] = newArr[j];  
                j = j - 1;  
            }  
            newArr[j + 1] = key;  
        }  
        return newArr;  
    }  
  
    public static int minEnergyWithSortedArray(int[] fruits) {  
        int[] sortedFruits = new int[0]; // 初始化一个空数组  
        int totalEnergy = 0;  
  
        // 逐个添加果子到数组,并排序  
        for (int fruit : fruits) {  
            sortedFruits = insertSort(sortedFruits, fruit);  
        }  
  
        // 现在 sortedFruits 是一个包含所有果子的已排序数组  
        // 使用类似于之前 PriorityQueue 的逻辑来合并果子  
        while (sortedFruits.length > 1) {  
            int combinedWeight = sortedFruits[0] + sortedFruits[1];  
            totalEnergy += combinedWeight;  
  
            // 创建一个新数组,不包含已合并的两个果子  
            int[] newFruits = new int[sortedFruits.length - 2];  
            System.arraycopy(sortedFruits, 2, newFruits, 0, newFruits.length);  
  
            // 将合并后的果子添加到新数组,并排序  
            sortedFruits = insertSort(newFruits, combinedWeight);  
        }  
  
        return totalEnergy;  
    }  
  
    public static void main(String[] args) {  
        Scanner sc = new Scanner(System.in);  
        int n = sc.nextInt();  
        int[] fruits = new int[n];  
        for (int i = 0; i < fruits.length; i++) {  
            fruits[i] = sc.nextInt();  
        }  
        System.out.println(minEnergyWithSortedArray(fruits));  
    }  
}

注意:
其实,普通队列使用了排序算法在每次添加数据的时候都进行排序,其实就是在模拟优先队列,所以我称之为"普通队列模拟优先队列".

分治问题

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

代码如下:

package exercise.luogu.graph;
  
import java.util.Scanner;  
  
public class P1908 {  
  
    private static long cnt = 0;  
    private static int[] tmp; // 用于归并排序的辅助数组  
  
    public static void main(String[] args) {  
        Scanner sc = new Scanner(System.in);  
        int n = sc.nextInt();  
        int[] arr = new int[n];  
  
        for (int i = 0; i < n; i++) {  
            arr[i] = sc.nextInt();  
        }  
  
        tmp = new int[n];  
        mergeSort(arr, 0, n - 1);  
        System.out.println(cnt);  
    }  
  
    public static void mergeSort(int[] arr, int l, int r) {  
        if (l >= r) {  
            return;  
        }  
  
        int mid = l + (r - l) / 2;  
        mergeSort(arr, l, mid);  
        mergeSort(arr, mid + 1, r);  
  
        merge(arr, l, mid, r);  
    }  
  
    public static void merge(int[] arr, int l, int mid, int r) {  
        int i = l, j = mid + 1, k = l;  
  
        while (i <= mid && j <= r) {  
            if (arr[i] <= arr[j]) {  
                tmp[k++] = arr[i++];  
            } else {  
                cnt += mid - i + 1; // 当前有序数组arr[i..mid]中,所有的元素都大于arr[j]  
                tmp[k++] = arr[j++];  
            }  
        }  
  
        while (i <= mid) {  
            tmp[k++] = arr[i++];  
        }  
  
        while (j <= r) {  
            tmp[k++] = arr[j++];  
        }  
  
        for (i = l, k = l; i <= r; ) {  
            arr[i++] = tmp[k++];  
        }  
    }  
}

注意:
要优化这段代码,我们可以考虑减少不必要的比较次数。当前的实现中,对于每个元素(arr[j]),都会与后面的所有元素进行比较,这导致时间复杂度为(O(n^2))。

一个更高效的算法是使用归并排序的思想来计算逆序对的数量。

在这个优化后的代码中,mergeSort函数用于递归地将数组分成更小的部分,并在merge函数中将这些部分合并起来。在合并的过程中,我们计算了逆序对的数量,并将其累加到cnt变量中。最后,打印出cnt即可得到数组中逆序对的总数。

动态规划问题

[NOIP1996 提高组] 挖地雷

题目描述

在一个地图上有 N   ( N ≤ 20 ) N\ (N \le 20) N (N20) 个地窖,每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式

有若干行。

1 1 1 行只有一个数字,表示地窖的个数 N N N

2 2 2 行有 N N N 个数,分别表示每个地窖中的地雷个数。

3 3 3 行至第 N + 1 N+1 N+1 行表示地窖之间的连接情况:

3 3 3 行有 n − 1 n-1 n1 个数( 0 0 0 1 1 1),表示第一个地窖至第 2 2 2 个、第 3 3 3 … \dots n n n 个地窖有否路径连接。如第 3 3 3 行为 11000 ⋯ 0 11000\cdots 0 110000,则表示第 1 1 1 个地窖至第 2 2 2 个地窖有路径,至第 3 3 3 个地窖有路径,至第 4 4 4 个地窖、第 5 5 5 … \dots n n n 个地窖没有路径。

4 4 4 行有 n − 2 n-2 n2 个数,表示第二个地窖至第 3 3 3 个、第 4 4 4 … \dots n n n 个地窖有否路径连接。

……

n + 1 n+1 n+1 行有 1 1 1 个数,表示第 n − 1 n-1 n1 个地窖至第 n n n 个地窖有否路径连接。(为 0 0 0 表示没有路径,为 1 1 1 表示有路径)。

输出格式

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。

样例 #1

样例输入 #1

5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1

样例输出 #1

1 3 4 5
27

提示

【题目来源】

NOIP 1996 提高组第三题

代码如下:
由于此题我代码仍有问题,仅供参考

package exercise.luogu.dp;

import java.util.*;

public class MineDigging {
    private static int maxMines = 0; // 记录挖到的最多地雷数  
    private static List<Integer>[] graph; // 地窖之间的连接图  
    private static int[] mines; // 每个地窖中的地雷数  
    private static boolean[] visited; // 记录地窖是否被访问过  

    public static void main(String[] args) {
        // 示例输入,可以根据实际情况修改
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();// 地窖数量
        mines = new int[n]; // 每个地窖中的地雷数
        for (int i = 0; i < mines.length; i++) {
            mines[i] = sc.nextInt();
        }
        Map<Integer, List<Integer>> edgesMap = new HashMap<>();
        int j = n - 1;
        while (j != 0) {
            List<Integer> edges = new ArrayList<>();

            for (int i = 0; i < j; i++) {
                edges.add(sc.nextInt());
            }
            edgesMap.put(n - j, edges);
            j--;
        }
        graph = new ArrayList[n]; // 初始化连接图

        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        // 添加连接路径,例如:0-1, 1-2, 1-3, 2-4(表示地窖0连接到地窖1,地窖1连接到地窖2和地窖3,等等)
        int index = 0;

        for (Map.Entry<Integer, List<Integer>> entry : edgesMap.entrySet()) {
            if (index == n - 1) {
                break;
            }
            for (int i = 0; i < entry.getValue().size(); i++) {

                if (entry.getValue().get(i) == 1) {
                    graph[index].add(i + 1);
                }
            }
            index++;
        }

        // 执行深度优先搜索来找到最多地雷的路径  
        visited = new boolean[n]; // 初始化访问数组
        for (int i = 0; i < n; i++) { // 从每个地窖开始尝试一次DFS
            dfs(i, 0); // 从地窖i开始,当前已经挖到的地雷数为0  
        }
        // 输出最多地雷数  
        System.out.println(maxMines);
    }

    private static void dfs(int current, int totalMines) {
        // 如果当前地窖已经被访问过,直接返回以避免重复计算  
        if (visited[current]) {
            return;
        }
        // 标记当前地窖为已访问并累加地雷数  
        visited[current] = true;
        totalMines += mines[current]; // 挖到当前地窖的地雷  
        // 更新最多地雷数  
        maxMines = Math.max(maxMines, totalMines);
        // 继续深度优先搜索相邻的地窖  
        for (int neighbor : graph[current]) {
            dfs(neighbor, totalMines); // 递归搜索相邻地窖  
        }
        // 回溯,标记当前地窖为未访问,以便其他路径可以访问它  
        visited[current] = false;
    }
}

二分查找

【深基13.例1】查找

题目描述

输入 n n n 个不超过 1 0 9 10^9 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_{n} a1,a2,,an,然后进行 m m m 次询问。对于每次询问,给出一个整数 q q q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 − 1 -1 1

输入格式

第一行 2 2 2 个整数 n n n m m m,表示数字个数和询问次数。

第二行 n n n 个整数,表示这些待查询的数字。

第三行 m m m 个整数,表示询问这些数字的编号,从 1 1 1 开始编号。

输出格式

输出一行, m m m 个整数,以空格隔开,表示答案。

样例 #1

样例输入 #1

11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

样例输出 #1

1 2 -1

提示

数据保证, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 0 ≤ a i , q ≤ 1 0 9 0 \leq a_i,q \leq 10^9 0ai,q109 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105

本题输入输出量较大,请使用较快的 IO 方式。

代码如下:
由于此题同还有两个点超时没有通过,代码仅供参考

package exercise.luogu.binary;

import java.util.Scanner;

public class P2249 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[] arr = new int[n];
        int[] crr = new int[m];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = sc.nextInt();
        }
        for (int i = 0; i < crr.length; i++) {
            crr[i] = sc.nextInt();
            int index = binarySearch(arr, crr[i]);
            if (index == -1) {
                System.out.print(-1 + " ");
            } else {
                System.out.print((index + 1) + " ");
            }
        }
    }

    public static int binarySearch(int[] arr, int findValue) {
        return binarySearchHelper(arr, 0, arr.length - 1, findValue, -1);
    }

    public static int binarySearchHelper(int[] arr, int left, int right, int findValue, int result) {
        if (left > right) {
            return result;
        }
        int mid = left + (right - left) / 2;
        if (arr[mid] < findValue) {
            return binarySearchHelper(arr, mid + 1, right, findValue, result);
        } else if (arr[mid] > findValue) {
            return binarySearchHelper(arr, left, mid - 1, findValue, result);
        } else {
            return binarySearchHelper(arr, left, mid - 1, findValue, mid);
        }
    }
}

求助

动态规划习题
二分查找习题
这两个题仍有问题,请看到的朋友们,尝试为我解答,谢谢!!

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知意..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值