├── .gitignore ├── 0JavaDocsAPI ├── JavaAPI.md ├── JavaKnowledge.md ├── README.md └── TemplateSummary.md ├── 1基础算法 ├── AcWing2816判断子序列.md ├── AcWing785快速排序.md ├── AcWing786第k个数.md ├── AcWing787归并排序.md ├── AcWing788逆序对的数量.md ├── AcWing789数的范围.md ├── AcWing790数的三次方根.md ├── AcWing791高精度加法.md ├── AcWing792高精度减法.md ├── AcWing793高精度乘法.md ├── AcWing794高精度除法.md ├── AcWing795前缀和.md ├── AcWing796子矩阵的和.md ├── AcWing797差分.md ├── AcWing798差分矩阵.md ├── AcWing799最长连续不重复子序列.md ├── AcWing800数组元素的目标和.md ├── AcWing801二进制中1的个数.md ├── AcWing802区间和.md ├── AcWing803区间合并.md ├── pics │ ├── image-20210217152344632.png │ ├── image-20210217153826737.png │ ├── image-20210217154038552.png │ └── image-20210217155111008.png ├── standard.md └── 基础算法题目与模板.md ├── 2数据结构 ├── 2数据结构题目与模板.md ├── AcWing143最大异或对.md ├── AcWing154滑动窗口.md ├── AcWing240食物链.md ├── AcWing3302表达式求值.md ├── AcWing826单链表.md ├── AcWing827双链表.md ├── AcWing828模拟栈.md ├── AcWing829模拟队列.md ├── AcWing830单调栈.md ├── AcWing831KMP字符串.md ├── AcWing835Trie字符串统计.md ├── AcWing836合并集合.md ├── AcWing837连通块中点的数量.md ├── AcWing838堆排序.md ├── AcWing840模拟散列表.md ├── AcWing841字符串哈希.md └── pics │ ├── image-20210217161426667.png │ ├── image-20210217161745736.png │ ├── image-20210217161943396.png │ ├── image-20210217192838725.png │ ├── image-20210217195208053.png │ ├── image-20210217195854304.png │ └── image-20210218150532815.png ├── 3搜索与图论 ├── 3搜索与图论模板.md ├── AcWing842排列数字.md ├── AcWing843n皇后问题.md ├── AcWing844走迷宫.md ├── AcWing845八数码.md ├── AcWing846树的重心.md ├── AcWing847图中点的层次.md ├── AcWing848有向图的拓扑序列.md ├── AcWing849Dijkstra求最短路I.md ├── AcWing851spfa求最短路.md ├── AcWing853有边数限制的最短路.md ├── AcWing854Floyd求最短路.md ├── AcWing858Prim算法求最小生成树.md ├── AcWing859Kruskal算法求最小生成树.md ├── AcWing860染色法判定二分图.md └── AcWing861二分图的最大匹配.md ├── 4数学知识 ├── 4数学知识题目与模板.md ├── AcWing866试除法判定质数.md ├── AcWing867分解质因数.md ├── AcWing868筛质数.md ├── AcWing869试除法求约数.md ├── AcWing870约数个数.md ├── AcWing871约数之和.md └── AcWing872最大公约数.md ├── 5动态规划 ├── 5动态规划.md ├── AcWing282石子合并.md ├── AcWing285没有上司的舞会.md ├── AcWing2_01背包问题.md ├── AcWing3完全背包问题.md ├── AcWing4多重背包问题I.md ├── AcWing5多重背包问题II.md ├── AcWing895最长上升子序列.md ├── AcWing896最长上升子序列II.md ├── AcWing897最长公共子序列.md ├── AcWing898数字三角形.md ├── AcWing899编辑距离.md ├── AcWing901滑雪.md ├── AcWing902最短编辑距离.md └── AcWing9分组背包问题.md ├── 6贪心 ├── AcWing905区间选点.md └── AcWing908最大不相交区间数量.md └── README.md /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/.gitignore -------------------------------------------------------------------------------- /0JavaDocsAPI/JavaAPI.md: -------------------------------------------------------------------------------- 1 | # Java API 2 | 3 | [TOC] 4 | 5 | ## java.lang 6 | 7 | ### Class Integer 8 | 9 | 1. 最大最小值 : `MAX_VALUE, MIN_VALUE`; 10 | 2. 十进制字符串转化为 int 整数 : `Integer.parseInt(String s)`; 11 | 3. 不同进制字符串转化为 int 整数 : `Integer.parseInt(String s, int radix)`; 12 | 4. 十进制字符串转化为 Integer 整数 : `Integer.valueOf(String s)`; 13 | 5. 不同进制字符串转化为 Integer 整数 : `Integer.valueOf(String s, int radix)`; 14 | 15 | ### Class String 16 | 17 | 1. 字符串转字符数组:`s.toCharArray()`; 18 | 2. 字符串长度: `s.length()`; 19 | 3. 数组转字符串 : `String.valueOf(char[] data)`; 20 | 4. 返回子字符串 : `s.substring(int beginIndex, int endIndex)`,左闭右开区间;`substring(int beginIndex)` 21 | 5. 字符串去除首尾空格 : `s.trim()`; 22 | 6. 使用特定切割符来分割字符串成一个字符串数组 : `s.split(String regex)`; 23 | 7. 使用特定切割符来分割字符串成一个字符串数组 : `s.split(String regex, int limit)`; 24 | 8. 判断字符串内容是否相同 : `equals(Object anObject)`; 25 | 9. 判断字符串和`StringBuilder`与`StringBuffer`是否相同 : `contentEquals(CharSequence cs);contentEquals(StringBuffer sb)`; 26 | 27 | 28 | ### Class StringBuilder 29 | 30 | 1. java.lang.StringBuilder 31 | 2. Constructor : `StringBuilder(); StringBuilder(String str)`; 32 | 3. 添加一个元素,参数 boolean, char, char[], double, float, int , long, String : `append(boolean b)`; 33 | 4. 返回相应下标的字符 : `charAt(int index)`; 34 | 5. 转化为字符串 : `toString()`; 35 | 6. 替换 : `s.replace()`;TODO时间复杂度 36 | 7. 翻转字符串 : `s.reverse()`; 37 | 8. 合并生成字符串 : `String.join(CharSequence delimiter, Iterable elements)`; 38 | 39 | 40 | ## java.util. 41 | 42 | ### Class Arrays 43 | 44 | 1. 排序 : `Arrays.sort(array)`; 45 | 2. 数组转化为列表 : `Arrays.asList()` 46 | 3. 复制数组 : `Arrays.copyOf(T[] original, int newLength)` 47 | 4. 数组排序,指定范围 : `Arrays.sort(Object[] a, int fromIndex, int toIndex)` 48 | 5. 将数组g的 (1,n) 的元素用Pair的第二个元素升序排序 : `Arrays.sort(T[] a, int fromIndex, int toIndex, Comparator c)`;例子如`Arrays.sort(g, 1, n + 1, (Pair p1, Pair p2) -> p1.b - p2.b);`左闭右开区间 49 | 6. 二维数组先根据第一个元素排序,再根据第二个元素排序 : 50 | ```java 51 | Arrays.sort(cbs, (o1, o2) -> { 52 | if(o1[0] != o2[0]) return o1[0] - o2[0]; 53 | else if(o1[1] != o2[1]) return o1[1] - o2[1]; 54 | else return o1[2] - o2[2]; 55 | }); 56 | ``` 57 | 7. 数组排序,重写比较器的写法: 58 | ```java 59 | Arrays.sort(newEdges, new Comparator(){ 60 | @Override 61 | public int compare(int[] o1, int[] o2){ 62 | return o1[2] - o2[2]; 63 | } 64 | }); 65 | ``` 66 | 67 | ### Interface Collection 68 | 69 | 70 | 1. All Known Subinterfaces: 71 | 72 | ```java 73 | BeanContext, BeanContextServices, BlockingDeque, BlockingQueue, Deque, List, NavigableSet, Queue, Set, SortedSet, TransferQueue 74 | ``` 75 | 76 | 2. All Known Implementing Classes: 77 | ```java 78 | AbstractCollection, AbstractList, AbstractQueue, AbstractSequentialList, AbstractSet, ArrayBlockingQueue, ArrayDeque, ArrayList, AttributeList, BeanContextServicesSupport, BeanContextSupport, ConcurrentHashMap.KeySetView, ConcurrentLinkedDeque, ConcurrentLinkedQueue, ConcurrentSkipListSet, CopyOnWriteArrayList, CopyOnWriteArraySet, DelayQueue, EnumSet, HashSet, JobStateReasons, LinkedBlockingDeque, LinkedBlockingQueue, LinkedHashSet, LinkedList, LinkedTransferQueue, PriorityBlockingQueue, PriorityQueue, RoleList, RoleUnresolvedList, Stack, SynchronousQueue, TreeSet, Vector 79 | ``` 80 | 81 | 3. 例子:Deque 转为 List,因为LinkedList有`LinkedList(Collection c)`的构造方法。 82 | 83 | ```java 84 | List<> list = new LinkedList(Deque); 85 | ``` 86 | 87 | ### Interface Deque 88 | 89 | 1. Summary of Deque methods 90 | 91 | ||First Element |(Head) |Last Element| (Tail)| 92 | |---|---|---|---|---| 93 | ||Throws exception|Special value|Throws exception|Special value| 94 | |Insert|addFirst(e)|offerFirst(e)|addLast(e)|offerLast(e)| 95 | |Remove|removeFirst()|pollFirst()|removeLast()|pollLast()| 96 | |Examine|getFirst()|peekFirst()|getLast()|peekLast()| 97 | 98 | 2. Queue (FIFO)Comparison of Queue and Deque methods 99 | 100 | Queue 队尾进元素,队首出元素 101 | 102 | |Queue Method|Equivalent Deque Method| 103 | |---|---| 104 | |add(e)|addLast(e)| 105 | |offer(e)|offerLast(e)| 106 | |remove()|removeFirst()| 107 | |poll()|pollFirst()| 108 | |element()|getFirst()| 109 | |peek()|peekFirst()| 110 | 111 | 3. Stack (LIFO) Comparison of Stack and Deque methods 112 | 113 | **Stack 队首进元素,队首出元素**。 114 | 115 | |Stack Method|Equivalent Deque Method| 116 | |---|---| 117 | |push(e)|addFirst(e)| 118 | |pop()|removeFirst()| 119 | |peek()|peekFirst()| 120 | 121 | 4. 返回大小 : `dq.size()`; 122 | 123 | ### Interface List 124 | 125 | 1. LinkedList实现的构造函数 : `LinkedList();LinkedList(Collection c)`; 126 | 2. 队尾添加元素 : `list.add(element)`; 127 | 3. 获取下标元素 : `list.get(int index)`; 128 | 4. 列表元素个数 : `list.size()`; 129 | 5. 列表转数组 : `list.toArray()`;//转化为int的比较繁琐 130 | 6. 列表是否为空 : `list.isEmpty()`; 131 | 7. 列表是否包含相应元素 : `list.contains(Object o)`; 132 | 133 | ### Interface Map 134 | 135 | 1. 得到键值,不存在就给默认值 : `map.getOrDefault(key, defaultValue)`; 136 | 2. 得到键值 : `map.get(key)`; 137 | 3. 是否包含key : `map.containsKey(Object key)`; 138 | 4. 返回map的值的集合 : `map.values()`; 可以用 LinkedList 的构造函数来转化为列表 139 | 140 | ### Regex.Class Pattern 141 | 142 | ### Class PriorityQueue 143 | 144 | 145 | > Implementation note: this implementation provides O(log(n)) time for the enqueuing and dequeuing methods (offer, poll, remove() and add); linear time for the remove(Object) and contains(Object) methods; and constant time for the retrieval methods (peek, element, and size). 146 | 147 | 148 | 1. PriorityQueue : Creates a PriorityQueue with the default initial capacity (11) that orders its elements according to their natural ordering. 自然顺序,默认小根堆。如果要构造大根堆要重写比较器`Queue pq = new PriorityQueue<>((v1, v2) -> v2 - v1)`。 149 | 2. 查看堆顶元素 : `pq.peek()`; 150 | 3. 添加元素 : `pq.add()`; 151 | 152 | ### Class Stack 153 | 154 | 不推荐 Stack 类来声明栈。因为 Stack 继承 Vector,Vector 是一个动态数组,可以在任意位置添加或者删除元素。因此 Stack 也有这个能力,这就破坏了封装成一个 Stack 的意义。一般用 Deque 来声明一个栈。 155 | 156 | ```java 157 | Deque stack = new ArrayDeque(); 158 | ``` 159 | 160 | --- 161 | --- 162 | ``` 163 | C++ STL简介 164 | vector, 变长数组,倍增的思想 165 | size() 返回元素个数 166 | empty() 返回是否为空 167 | clear() 清空 168 | front()/back() 169 | push_back()/pop_back() 170 | begin()/end() 171 | [] 172 | 支持比较运算,按字典序 173 | 174 | pair 175 | first, 第一个元素 176 | second, 第二个元素 177 | 支持比较运算,以first为第一关键字,以second为第二关键字(字典序) 178 | 179 | string,字符串 180 | size()/length() 返回字符串长度 181 | empty() 182 | clear() 183 | substr(起始下标,(子串长度)) 返回子串 184 | c_str() 返回字符串所在字符数组的起始地址 185 | 186 | queue, 队列 187 | size() 188 | empty() 189 | push() 向队尾插入一个元素 190 | front() 返回队头元素 191 | back() 返回队尾元素 192 | pop() 弹出队头元素 193 | 194 | priority_queue, 优先队列,默认是大根堆 195 | size() 196 | empty() 197 | push() 插入一个元素 198 | top() 返回堆顶元素 199 | pop() 弹出堆顶元素 200 | 定义成小根堆的方式:priority_queue, greater> q; 201 | 202 | stack, 栈 203 | size() 204 | empty() 205 | push() 向栈顶插入一个元素 206 | top() 返回栈顶元素 207 | pop() 弹出栈顶元素 208 | 209 | deque, 双端队列 210 | size() 211 | empty() 212 | clear() 213 | front()/back() 214 | push_back()/pop_back() 215 | push_front()/pop_front() 216 | begin()/end() 217 | [] 218 | 219 | set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列 220 | size() 221 | empty() 222 | clear() 223 | begin()/end() 224 | ++, -- 返回前驱和后继,时间复杂度 O(logn) 225 | 226 | set/multiset 227 | insert() 插入一个数 228 | find() 查找一个数 229 | count() 返回某一个数的个数 230 | erase() 231 | (1) 输入是一个数x,删除所有x O(k + logn) 232 | (2) 输入一个迭代器,删除这个迭代器 233 | lower_bound()/upper_bound() 234 | lower_bound(x) 返回大于等于x的最小的数的迭代器 235 | upper_bound(x) 返回大于x的最小的数的迭代器 236 | map/multimap 237 | insert() 插入的数是一个pair 238 | erase() 输入的参数是pair或者迭代器 239 | find() 240 | [] 注意multimap不支持此操作。 时间复杂度是 O(logn) 241 | lower_bound()/upper_bound() 242 | 243 | unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表 244 | 和上面类似,增删改查的时间复杂度是 O(1) 245 | 不支持 lower_bound()/upper_bound(), 迭代器的++,-- 246 | 247 | bitset, 圧位 248 | bitset<10000> s; 249 | ~, &, |, ^ 250 | >>, << 251 | ==, != 252 | [] 253 | 254 | count() 返回有多少个1 255 | 256 | any() 判断是否至少有一个1 257 | none() 判断是否全为0 258 | 259 | set() 把所有位置成1 260 | set(k, v) 将第k位变成v 261 | reset() 把所有位变成0 262 | flip() 等价于~ 263 | flip(k) 把第k位取反 264 | 265 | 266 | 作者:yxc 267 | 链接:https://www.acwing.com/blog/content/404/ 268 | 来源:AcWing 269 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 270 | ``` -------------------------------------------------------------------------------- /0JavaDocsAPI/JavaKnowledge.md: -------------------------------------------------------------------------------- 1 | # Java API Knowledge 2 | [toc] 3 | 4 | ## BufferedReader 和 BufferedWriter 5 | ```java 6 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 7 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 8 | in.readLine(); 9 | out.write(); 10 | ``` 11 | ## Java 声明、定义、赋值、初始化、变量、引用、创建的区别 12 | 13 | **变量**:由基本数据类型定义的变量称为普通变量,由类、数字、接口类型定义的变量称为引用型变量 14 | 15 | **声明**:`int x;`声明不需要分配存储空间,只作用于编译器; 16 | 17 | **定义**: `int x =0;`声明变量并进行初始化的过程称为定义,定义的方式包括声明、引用、创建、初始化;与声明的区别在于是否分配存储空间; 18 | 19 | **初始化**: 从定义变量时的赋值或引用的过程称为初始化,也就是第一次赋值或者引用 20 | 21 | **赋值**: 普通变量赋值可以不断更新栈中存储的数据,而对象引用用的是同一块堆内存 22 | 23 | **引用**: 变量名 = 对象,这个等于的过程就是引用,也称为变量名指向一个对象;注意创建的变量名储存在栈中,而引用是指向堆内存,栈中存的是堆内存的首地址,因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存; 24 | 25 | **创建**: 对于引用型变量,在内存中new(开辟)空间的过程称为创建,开辟的内存空间为堆内存 26 | 27 | ```java 28 | int a; // 声明了一个类型为int,变量名为a的普通变量; 29 | int a=1; // 定义了一个类型为int,变量名为a,初始化值为1的普通变量; 30 | a=2; // 给已经声明了的变量a赋值为2; 31 | String s; // 声明了一个类型为String 类,变量名为s的引用型变量 32 | String s=null; // 定义了一个类型为String 类,变量名为s的引用型变量,初始化值为空; 33 | String s=“123”; // "123"存储在String 常量池中,s指向存储"123"的地址(常量池:常量池在编译期间就将一部分数据存放于该区域,包含以final修饰的基本数据类型的常量值、String字符串) 34 | String s=new String(“123”); // 定义了一个类型为String 类,变量名为s的引用型变量,创建一个String对象初始化值为"123" 35 | ``` 36 | 37 | ## static 和 final 讲解 38 | 39 | **可修饰部分**: 40 | 41 | 1. static:成员变量、方法、代码块(静态代码块)、内部类(静态内部类) 42 | 2. final: 类、成员变量、方法、局部变量 43 | 3. final在一个对象类唯一,static final在多个对象中都唯一; 44 | 45 | **static**: 46 | 47 | 1. static的主要作用是:方便在没有创建对象的情况下来进行调用(方法/变量)。 48 | 2. static 修饰变量 49 | 1. 无论一个类生成了多少个对象,所有这个对象共用唯一一份静态的成员变量;一个对象对该静态成员变量进行了修改,其他对象的该静态成员变量的值也会随之发生变化。如果一个成员变量是static的,那么我们可以通过类名.成员变量名的方式来使用它(推荐使用这种方式)。 50 | 2. 没有被static修饰的变量,叫实例变量。对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。 51 | 3. static 修饰方法 52 | 1. **static方法可以直接通过类名调用,任何的实例也都可以调用,static方法只能访问static的变量和方法**,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。 53 | 2. static的数据或方法,属于整个类的而不是属于某个对象的,是不会和类的任何对象实例联系到一起。所以子类和父类之间可以存在同名的static方法名。 54 | 3. 静态方法只能继承,不能重写(Override)。 55 | 4. static 修饰类 56 | 1. 一般的类是没有static的,只有内部类可以加上static来表示嵌套类。 57 | 5. static 和 final一起用来修饰成员变量和成员方法,可简单理解为“全局常量” 58 | 1. 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。 59 | 2. 对于方法,表示不可覆盖,并且可以通过类名直接访问。 60 | 3. 对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。 61 | ```java 62 | private static final String strStaticFinalVar = "aaa"; 63 | private static String strStaticVar = null; 64 | private final String strFinalVar= null; 65 | private static final int intStaticFinalVar = 0; 66 | private static final Integer integerStaticFinalVar = new Integer(8); 67 | private static final ArrayList alStaticFinalVar = new ArrayList(); 68 | 69 | //strStaticFinalVar="bbb";//错误,final表示终态,不可以改变变量本身. 70 | strStaticVar = "bbb"; //正确,static表示类变量,值可以改变. 71 | //strFinalVar="呵呵呵呵"; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 72 | //intStaticFinalVar=2; //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 73 | //integerStaticFinalVar=new Integer(8); //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。 74 | alStaticFinalVar.add("aaa"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 75 | alStaticFinalVar.add("bbb"); //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。 76 | ``` 77 | 6. static 静态代码块 78 | 1. static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。 79 | 2. 静态块要放在声明的变量下面。 80 | 3. 如果继承体系既有构造方法,又有静态代码块,那么首先执行最顶层的类的静态代码块,一直执行到最底层类的静态代码块,然后再去执行最顶层类的构造方法,一直执行到最底层的构造方法,注意:**静态代码块只会执行一次**。 81 | ```java 82 | public class Main { 83 | public static void main(String[] args) { 84 | new R(); 85 | new R(); 86 | } 87 | } 88 | class P{ 89 | //static静态代码块 90 | static { 91 | System.out.println("P static block"); 92 | } 93 | public P(){ 94 | System.out.println("P constructor"); 95 | } 96 | } 97 | class Q extends P{ 98 | static { 99 | System.out.println("Q static block"); 100 | } 101 | public Q(){ 102 | System.out.println("Q constructor"); 103 | } 104 | } 105 | class R extends Q{ 106 | static { 107 | System.out.println("R static block"); 108 | } 109 | public R(){ 110 | System.out.println("R constructor"); 111 | } 112 | } 113 | ``` 114 | 4. 那就是当我们初始化static修饰的成员时,可以将他们统一放在一个以static开始,用花括号包裹起来的块状语句中: 115 | 7. 静态导包:不同于非static导入,采用static导入包后,在不与当前类的方法名冲突的情况下,无需使用“类名.方法名”的方法去调用类方法了,直接可以采用"方法名"去调用类方法,就好像是该类自己的方法一样使用即可。 116 | 117 | **final**: 118 | 119 | 1. final修饰类:final类不能被继承,没有子类,final类中的方法默认是final的。 120 | 2. final修饰方法: 当一个方法被final所修饰的时,表示该方法是一个终态方法,即不能被重写(Override)。但可以被继承。 121 | 3. 类中所有的private方法都被隐含是final的。由于无法取用private方法,则也无法重载之。 122 | 4. final修饰变量: 123 | 1. 基础类型 用fianl修饰后就变成了一个常量,不可以重新赋值。 124 | 2. 包装类型 用final修饰后该变量指向的地址不变,但是该地址的的变量完全可以改变。 125 | 5. 对于final类型成员变量 126 | 6. final修饰方法参数 127 | ## Scanner和BufferedReader的区别和用法 128 | 129 | Scanner 和 BufferedReader 同样能实现将键盘输入的数据送入程序 130 | 131 | **BufferedReader**: 132 | 133 | 1. BufferedReader 对象只将回车看作输入结束,得到的字符串 134 | 2. BufferedReader 是字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取!速度要比Scanner快!而且也可以设置缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。 135 | 3. BufferedReader类位于java.io包中,所以要使用这个类,就要引入java.io这个包 136 | 4. `import java.io.BufferedReader.readLine()`方法会返回用户在按下Enter键之前的所有字符输入,不包括最后按下的Enter返回字符.使用BufferedReader对象的readLine()方法必须处理java.io.IOException异常(Exception).使用BufferedReader来取得输入,理解起来要复杂得多.但是使用这个方法是固定的,每次使用前先如法炮制就可以了 137 | 138 | **Scanner**: 139 | 1. Scanner对象把回车,空格,tab键都看作输入结束,直接用sc.next()得到的是字符串形式 140 | 2. 在创建Scanner类的对象时,需要用System.in作为它的参数,也可以将Scanner看作是System.in对象的支持者,System.in取得用户输入的内容后,交给Scanner来作一些处理 141 | ```java 142 | Scanner类中提供了多个方法: 143 | next():取得一个字符串; 144 | nextInt():将取得的字符串转换成int类型的整数; 145 | nextFloat():将取得的字符串转换成float型; 146 | nextBoolean():将取得的字符串转换成boolean型; 147 | ``` 148 | 3. Scanner类位于java.util包中,要加上import java.util.Scanner; 用Scanner获得用户的输入非常的方便,但是Scanner取得输入的依据是空格符,包括空格键,Tab键和Enter键.当按下这其中的任一键时,Scanner就会返回下一个输入.当你输入的内容中间包括空格时,显然,使用Scanner就不能完整的获得你输入的字符串.这时候我们可以考虑使用BufferedReader类取得输入.其实在Java SE 1.4及以前的版本中,尚没有提供Scanner方法,我们获得输入时也是使用BufferReader的. 149 | 150 | ## ArrayList 和 LinkedList 151 | 152 | - ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。 153 | - LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。 154 | - 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 155 | - LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 156 | - ArrayList的实现用的是数组,LinkedList是基于链表,**ArrayList适合查找**,**LinkedList适合增删**。 157 | 158 | ## Stack 和 Deque 159 | 160 | 一个更加完整,一致的,后进先出的栈相关的操作,应该由 Deque 接口提供。并且,也推荐使用 Deque 这种数据结构(比如 ArrayDeque)来实现。 161 | 162 | ```java 163 | Deque stack = new ArrayDeque(); 164 | ``` 165 | 166 | 1. Java 中的 Stack 类到底怎么了? 167 | 168 | Java 中的 Stack 类,最大的问题是,继承了 Vector 这个类。Vector 就是一个动态数组。继承使得子类继承了父类的所有公有方法。而 Vector 作为动态数组,是有能力在数组中的任何位置添加或者删除元素的。因此,Stack 继承了 Vector,Stack 也有这样的能力。这就破坏了栈这种数据结构的封装。 169 | 170 | 2. 为什么使用接口? 171 | 172 | 接口在 OOP 设计中,也是非常重要的概念。并且,在近些年,变得越来越重要。甚至发展出了“面向接口的编程”这一思想(Interface-based programming)。 173 | 174 | 在 Java 语言中,Queue 就是一个接口。我们想实现一个队列,可以这么写: 175 | 176 | ```java 177 | Queue q1 = new LinkedList<>(); 178 | Queue q2 = new ArrayDeque<>(); 179 | ``` 180 | 181 | q1 和 q2 的底层具体实现不同,一个是 LinkedList,一个是 ArrayDeque。但是,从用户的角度看,q1 和 q2 是一致的:都是一个队列,只能执行队列规定的方法。 182 | 183 | 底层开发人员可以随意维护自己的 LinkedList 类或者 ArrayDeque 类,只要他们满足 Queue 接口规定的规范;**接口的设计相当于做了访问限制。** 184 | 185 | 3. LinkedList 还是 ArrayQueue 来实现 Deque 186 | 187 | Java 官方推荐的创建栈的方式,使用了 Deque 接口。并且,在底层实现上,使用了 ArrayDeque,也就是基于动态数组的实现。为什么? 188 | 189 | 动态数组是可以进行扩容操作的。在触发扩容的时候,时间复杂度是 O(n) 的,但整体平均时间复杂度(Amortized Time)是 O(1)。 190 | 191 | 动态数组是可以进行扩容操作的。在触发扩容的时候,时间复杂度是 O(n) 的,但整体平均时间复杂度(Amortized Time)是 O(1)。 192 | 193 | 虽然如此,可是实际上,当数据量达到一定程度的时候,链表的性能是远远低于动态数组的。 194 | 195 | 这是因为,对于链表来说,每添加一个元素,都需要重新创建一个 Node 类的对象,也就是都需要进行一次 new 的内存操作。而对内存的操作,是非常慢的。 196 | 197 | 因此,甚至有人建议:在实践中,尤其是面对大规模数据的时候,不应该使用链表! 198 | 199 | 4. Vector 200 | 201 | 实际上,Vector 这个类不仅仅是简单的一个动态数组而已,而更进一步,保证了线程安全。 202 | 203 | 因为要保证线程安全,所以 Vector 实际上效率也并不高。 204 | 205 | Java 官方的 Vector 文档中明确指出了:如果你的应用场景不需要线程安全的特性,那么对于动态数组,应该使用 ArrayList。 206 | 207 | ## map 和 set 208 | 209 | ## java箭头函数,lambda表达式 210 | 211 | ## 常用数据结构的时间复杂度 212 | 213 | |Data Structure|新增|查询/Find|删除/Delete|GetByIndex| 214 | |---|---|---|---|---| 215 | |数组 Array (T[]) | O(n) | O(n) |O(n) | O(1) 216 | |链表 Linked list (LinkedList) | O(1)| O(n) | O(n) | O(n) 217 | Resizable array list (List) | O(1) |O(n) | O(n) | O(1) 218 | |Stack (Stack) | O(1) | - | O(1) | -| 219 | |Queue (Queue) |O(1) |- | O(1) | -| 220 | |Hash table (Dictionary) | O(1) | O(1) | O(1) | -| 221 | |Tree-based dictionary(SortedDictionary) | O(log n) | O(log n) | O(log n)| -| 222 | |Hash table based set (HashSet) | O(1) | O(1) | O(1) | -| 223 | |Tree based set (SortedSet) | O(log n) | O(log n) |O(log n) | -| 224 | 225 | ## Java 运算符优先级 226 | 227 | 下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。 228 | 229 | |类别|操作符|关联性| 230 | |:---:|:---:|---| 231 | |后缀|() [] . (点操作符)|左到右| 232 | |一元|++ -- ! ~|从右到左| 233 | |乘性|* / %|左到右| 234 | |加性|+ -|左到右| 235 | |移位|>> >>> <<|左到右| 236 | |关系|> >= < <=|左到右| 237 | |相等|== !=|左到右| 238 | |按位与|&|左到右| 239 | |按位异或|^|左到右| 240 | |按位或|\||左到右| 241 | |逻辑与|&&|左到右| 242 | |逻辑或|\| \||左到右| 243 | |条件|? :|从右到左| 244 | |赋值|= += -= *= /= %= >>= <<= &= ^= \|=|从右到左| 245 | |逗号|,|左到右| 246 | 247 | ## 由数据范围反推算法复杂度以及算法内容 248 | 249 | 一般ACM或者笔试题的时间限制是1秒或2秒。 250 | 在这种情况下,C++代码中的操作次数控制在 $10^7$ 为最佳。 251 | 252 | 下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择: 253 | 254 | 1. $n≤30$, 指数级别, dfs+剪枝,状态压缩dp 255 | 2. $n≤100 => O(n^3)$,floyd,dp 256 | 3. $n≤1000 => O(n^2),O(n^2logn)$,dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford 257 | 4. $n≤10000 => O(n∗\sqrt{n})$,块状链表、分块、莫队 258 | 5. $n≤100000 => O(nlogn)$ => 各种sort,线段树、树状数组、set/map、heap、dijkstra+heap、6. prim+heap、spfa、求凸包、求半平面交、二分 259 | 6. $n≤1000000 => O(n), 以及常数较小的 O(nlogn) 算法 => hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)的做法:sort、树状数组、heap、dijkstra、spfa 260 | 7. $n≤10000000 => O(n)$,双指针扫描、kmp、AC自动机、线性筛素数 261 | 8. $n≤10^9 => O(\sqrt{n})$,判断质数 262 | 9. $n≤10^{18} => O(logn)$,最大公约数,快速幂 263 | 10. $n≤10^{1000} => O((logn)^2)$,高精度加减乘除 264 | 11. $n≤10^{100000} => O(logn×loglogn)$,高精度加减、FFT/NTT 265 | 266 | 作者:yxc 267 | 链接:https://www.acwing.com/blog/content/32/ 268 | 来源:AcWing 269 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -------------------------------------------------------------------------------- /0JavaDocsAPI/README.md: -------------------------------------------------------------------------------- 1 | # Java Docs API 2 | 3 | ## JavaAPI.md 4 | 5 | 记录常用 API 用法。 6 | 7 | ## JavaKnowledge 8 | 9 | Java 稍微底层的一些知识点。比如复杂度分析,优劣对比等 10 | 11 | -------------------------------------------------------------------------------- /1基础算法/AcWing2816判断子序列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 贪心专题 2 | 3 | ## AcWing 2816. 判断子序列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm。 10 | 11 | 请你判断 a 序列是否为 b 序列的子序列。 12 | 13 | 子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}的一个子序列。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数 n,m。 18 | 19 | 第二行包含 n 个整数,表示 a1,a2,…,an。 20 | 21 | 第三行包含 m 个整数,表示 b1,b2,…,bm。 22 | 23 | **输出格式** 24 | 25 | 如果 a 序列是 b 序列的子序列,输出一行 Yes。 26 | 27 | 否则,输出 No。 28 | 29 | **数据范围** 30 | 31 | $1≤n≤m≤10^5,$ 32 | $−10^9≤ai,bi≤10^9$ 33 | 34 | ``` 35 | 输入样例: 36 | 37 | 3 5 38 | 1 3 5 39 | 1 2 3 4 5 40 | 41 | 输出样例: 42 | 43 | Yes 44 | ``` 45 | 46 | ### Solution 47 | 48 | ```java 49 | import java.util.*; 50 | import java.io.*; 51 | 52 | class Main{ 53 | static int N = 100010; 54 | static int[] a = new int[N]; 55 | static int[] b = new int[N]; 56 | public static void main(String[] args) throws IOException{ 57 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 58 | String[] s = br.readLine().split(" "); 59 | int n = Integer.parseInt(s[0]), m = Integer.parseInt(s[1]); 60 | s = br.readLine().split(" "); 61 | for(int i = 1; i <= n; i++) a[i] = Integer.parseInt(s[i - 1]); 62 | s = br.readLine().split(" "); 63 | for(int i = 1; i <= m; i++) b[i] = Integer.parseInt(s[i - 1]); 64 | int i = 1, j = 1; 65 | // a 是否是 b 的子序列 66 | // 从 b 里按顺序找 a 的每一个字符 67 | while(i <= n && j <= m){ 68 | // 找到了 i 往前走 69 | if(a[i] == b[j]) i++; 70 | j++; 71 | } 72 | if(i == n + 1) System.out.println("Yes"); 73 | else System.out.println("No"); 74 | } 75 | } 76 | ``` 77 | 78 | 双指针,感觉这样写会看着舒服些 79 | 80 | ```java 81 | import java.util.*; 82 | import java.io.*; 83 | 84 | class Main{ 85 | static final int N = 100010; 86 | static int[] a = new int[N]; 87 | static int[] b = new int[N]; 88 | public static void main(String[] args) throws IOException{ 89 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 90 | String[] s = br.readLine().split(" "); 91 | int n = Integer.parseInt(s[0]); 92 | int m = Integer.parseInt(s[1]); 93 | s = br.readLine().split(" "); 94 | for(int i = 0; i < n; i++) a[i] = Integer.parseInt(s[i]); 95 | s = br.readLine().split(" "); 96 | for(int i = 0; i < m; i++) b[i] = Integer.parseInt(s[i]); 97 | int i = 0, j = 0; 98 | for(; i < n; i++, j++){ 99 | while(j < m && a[i] != b[j]) j++; 100 | if(j == m){ 101 | break; 102 | } 103 | } 104 | if(i == n) System.out.println("Yes"); 105 | else System.out.println("No"); 106 | } 107 | } 108 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing785快速排序.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 785. 快速排序 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定你一个长度为n的整数数列。 10 | 11 | 请你使用快速排序对这个数列按照从小到大进行排序。 12 | 13 | 并将排好序的数列按顺序输出。 14 | 15 | **输入格式** 16 | 17 | 输入共两行,第一行包含整数 n。 18 | 19 | 第二行包含 n 个整数(所有整数均在$1~10^9$范围内),表示整个数列。 20 | 21 | **输出格式** 22 | 23 | 输出共一行,包含 n 个整数,表示排好序的数列。 24 | 25 | **数据范围** 26 | 27 | 1 ≤ n ≤ 100000 28 | 29 | **输入样例**: 30 | 31 | ``` 32 | 5 33 | 3 1 2 4 5 34 | ``` 35 | 36 | **输出样例**: 37 | 38 | ``` 39 | 1 2 3 4 5 40 | ``` 41 | 42 | ### Solution 43 | 44 | ```java 45 | import java.util.*; 46 | import java.io.*; 47 | 48 | public class Main{ 49 | public static void main(String[] args) throws IOException{ 50 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 51 | int n = Integer.parseInt(in.readLine()); 52 | int[] q = new int[n]; 53 | String[] s = in.readLine().split(" "); 54 | for(int i = 0; i < n; i++){ 55 | q[i] = Integer.parseInt(s[i]); 56 | } 57 | quickSort(q, 0, n - 1); 58 | for(int i = 0; i < n; i++){ 59 | System.out.print(q[i] + " "); 60 | } 61 | in.close(); 62 | } 63 | 64 | public static void quickSort(int[] q, int l, int r){ 65 | if(l >= r) return; 66 | int i = l - 1, j = r + 1, x = q[l + r >> 1]; 67 | while (i < j){ 68 | do i++; while(q[i] < x); 69 | do j--; while(q[j] > x); 70 | if(i < j) { 71 | int t = q[i]; 72 | q[i] = q[j]; 73 | q[j] = t; 74 | } 75 | } 76 | quickSort(q, l, j); 77 | quickSort(q, j + 1, r); 78 | } 79 | } 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /1基础算法/AcWing786第k个数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 786. 第k个数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为n的整数数列,以及一个整数k,请用快速选择算法求出数列从小到大排序后的第k个数。 10 | 11 | **输入格式** 12 | 13 | 输入共两行,第一行包含两个整数 n 和 k。 14 | 15 | 第二行包含 n 个整数(所有整数均在$1~10^9$范围内),表示整个数列。 16 | 17 | **输出格式** 18 | 19 | 输出一个整数,表示数列的第k小数。 20 | 21 | **数据范围** 22 | 23 | 1 ≤ n ≤ 100000 24 | 1 ≤ k ≤ n 25 | 26 | **输入样例**: 27 | 28 | ``` 29 | 5 3 30 | 2 4 1 5 3 31 | ``` 32 | 33 | **输出样例**: 34 | 35 | ``` 36 | 3 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | import java.io.*; 44 | 45 | class Main{ 46 | static int N = 100010; 47 | static int[] q = new int[N]; 48 | public static void main(String[] args) throws IOException{ 49 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 50 | String[] s = br.readLine().split(" "); 51 | int n = Integer.parseInt(s[0]); 52 | int k = Integer.parseInt(s[1]); 53 | s = br.readLine().split(" "); 54 | for(int i = 0; i < n; i++){ 55 | q[i] = Integer.parseInt(s[i]); 56 | } 57 | quickSort(0, n - 1, k); 58 | System.out.println(q[k - 1]); 59 | } 60 | public static void quickSort(int l, int r, int k){ 61 | if(l >= r) return; 62 | int i = l - 1, j = r + 1, x = q[l + r >> 1]; 63 | while(i < j){ 64 | do i++; while(q[i] < x); 65 | do j--; while(q[j] > x); 66 | if(i < j){ 67 | int t = q[i]; 68 | q[i] = q[j]; 69 | q[j] = t; 70 | } 71 | 72 | } 73 | // 第 k 数字的下标为 k - 1 74 | // 如果第 k 个数字在 j 的右边就递归右边 75 | // 否则递归左边 76 | // 这样可以把时间复杂度优化为 O(n) 77 | if(k - 1 > j) quickSort(j + 1, r, k); 78 | else quickSort(l, j, k); 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /1基础算法/AcWing787归并排序.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 787. 归并排序 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定你一个长度为n的整数数列。 10 | 11 | 请你使用快速排序对这个数列按照从小到大进行排序。 12 | 13 | 并将排好序的数列按顺序输出。 14 | 15 | **输入格式** 16 | 17 | 输入共两行,第一行包含整数 n。 18 | 19 | 第二行包含 n 个整数(所有整数均在$1~10^9$范围内),表示整个数列。 20 | 21 | **输出格式** 22 | 23 | 输出共一行,包含 n 个整数,表示排好序的数列。 24 | 25 | **数据范围** 26 | 27 | 1 ≤ n ≤ 100000 28 | 29 | **输入样例**: 30 | 31 | ``` 32 | 5 33 | 3 1 2 4 5 34 | ``` 35 | 36 | **输出样例**: 37 | 38 | ``` 39 | 1 2 3 4 5 40 | ``` 41 | 42 | ### Solution 43 | 44 | ```java 45 | import java.util.*; 46 | import java.io.*; 47 | 48 | public class Main{ 49 | public static void main(String[] args) throws IOException{ 50 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 51 | int n = Integer.parseInt(in.readLine()); 52 | int[] q = new int[n]; 53 | int[] tmp = new int[n]; 54 | String[] s = in.readLine().split(" "); 55 | for(int i = 0; i < n; i++){ 56 | q[i] = Integer.parseInt(s[i]); 57 | } 58 | mergeSort(q, 0, n - 1, tmp); 59 | for(int num : q){ 60 | System.out.print(num + " "); 61 | } 62 | } 63 | 64 | public static void mergeSort(int[] q, int l, int r, int[] tmp){ 65 | if(l >= r) return; 66 | int mid = l + r >> 1; 67 | mergeSort(q, l, mid, tmp); 68 | mergeSort(q, mid + 1, r, tmp); 69 | int k = 0, i = l, j = mid + 1; 70 | while(i <= mid && j <= r){ 71 | if(q[i] <= q[j]) tmp[k++] = q[i++]; 72 | else tmp[k++] = q[j++]; 73 | } 74 | while(i <= mid) tmp[k++] = q[i++]; 75 | while(j <= r) tmp[k++] = q[j++]; 76 | for(i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j]; 77 | } 78 | } 79 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing788逆序对的数量.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 788. 逆序对的数量 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为n的整数数列,请你计算数列中的逆序对的数量。 10 | 11 | 逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对;否则不是。 12 | 13 | **输入格式** 14 | 15 | 第一行包含整数n,表示数列的长度。 16 | 17 | 第二行包含 n 个整数,表示整个数列。 18 | 19 | **输出格式** 20 | 21 | 输出一个整数,表示逆序对的个数。 22 | 23 | **数据范围** 24 | 25 | $1≤n≤100000$ 26 | 27 | ```r 28 | 输入样例: 29 | 30 | 6 31 | 2 3 4 5 6 1 32 | 33 | 输出样例: 34 | 35 | 5 36 | ``` 37 | 38 | ### Solution 39 | 40 | 1. 分析左右两半部分,如果左半部分 q[i] 大于右半部分的 q[j],那么从 i 到 mid 都可以和 j 组成逆序对,逆序对个数 res += mid - i + 1 41 | 42 | ```java 43 | import java.util.*; 44 | import java.io.*; 45 | 46 | class Main{ 47 | static int N = 100010; 48 | static long res = 0; 49 | static int[] q = new int[N]; 50 | static int[] tmp = new int[N]; 51 | public static void main(String[] args) throws IOException{ 52 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 53 | int n = Integer.parseInt(br.readLine()); 54 | String[] s = br.readLine().split(" "); 55 | for(int i = 0; i < n; i++){ 56 | q[i] = Integer.parseInt(s[i]); 57 | } 58 | 59 | mergeSort(0, n - 1); 60 | System.out.println(res); 61 | } 62 | public static void mergeSort(int l, int r){ 63 | if(l >= r) return; 64 | int mid = l + r >> 1; 65 | mergeSort(l, mid); 66 | mergeSort(mid + 1, r); 67 | int k = 0, i = l, j = mid + 1; 68 | while(i <= mid && j <= r){ 69 | if(q[i] <= q[j]) tmp[k++] = q[i++]; 70 | else { 71 | res += mid - i + 1; 72 | tmp[k++] = q[j++]; 73 | } 74 | } 75 | while(i <= mid) tmp[k++] = q[i++]; 76 | while(j <= r) tmp[k++] = q[j++]; 77 | for(i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j]; 78 | } 79 | } 80 | ``` 81 | 82 | ### yxc 83 | 84 | ![image-20210217152344632](pics/image-20210217152344632.png) -------------------------------------------------------------------------------- /1基础算法/AcWing789数的范围.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 789. 数的范围 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。 10 | 11 | 对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。 12 | 13 | 如果数组中不存在该元素,则返回“-1 -1”。 14 | 输入格式 15 | 16 | 第一行包含整数n和q,表示数组长度和询问个数。 17 | 18 | 第二行包含n个整数(均在1~10000范围内),表示完整数组。 19 | 20 | 接下来q行,每行包含一个整数k,表示一个询问元素。 21 | 输出格式 22 | 23 | 共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。 24 | 25 | 如果数组中不存在该元素,则返回“-1 -1”。 26 | 27 | **数据范围** 28 | ```r 29 | 1≤n≤100000 30 | 1≤q≤10000 31 | 1≤k≤10000 32 | ``` 33 | **输入样例**: 34 | ```r 35 | 6 3 36 | 1 2 2 3 3 4 37 | 3 38 | 4 39 | 5 40 | ``` 41 | **输出样例**: 42 | ```r 43 | 3 4 44 | 5 5 45 | -1 -1 46 | ``` 47 | 48 | ### Solution 49 | 50 | ```java 51 | import java.util.*; 52 | import java.io.*; 53 | 54 | public class Main{ 55 | public static void main(String[] args) throws IOException{ 56 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 57 | String[] s = in.readLine().split(" "); 58 | int n = Integer.parseInt(s[0]); 59 | int q = Integer.parseInt(s[1]); 60 | s = in.readLine().split(" "); 61 | int[] a = new int[n]; 62 | for(int i = 0; i < n; i++){ 63 | a[i] = Integer.parseInt(s[i]); 64 | } 65 | for(int i = 0; i < q; i++){ 66 | int k = Integer.parseInt(in.readLine()); 67 | // low 起始位置,high 终止位置 68 | int low = 0, high = 0; 69 | // 查找第一个值等于给定值的元素 70 | int l = 0, r = n - 1; 71 | while(l <= r){ 72 | int mid = l + r >> 1; 73 | if(a[mid] > k) r = mid - 1; 74 | else if(a[mid] < k) l = mid + 1; 75 | else{ 76 | if(mid == 0 || a[mid - 1] != k) { 77 | low = mid; 78 | break; 79 | } 80 | else r = mid - 1; 81 | } 82 | } 83 | if(a[low] != k){ 84 | System.out.println("-1 -1"); 85 | }else{ 86 | // 查找最后一个值等于给定值的元素 87 | l = 0; 88 | r = n - 1; 89 | while(l <= r){ 90 | int mid = l + r >> 1; 91 | if(a[mid] > k) r = mid - 1; 92 | else if(a[mid] < k) l = mid + 1; 93 | else{ 94 | if(mid == n - 1 || a[mid + 1] != k){ 95 | high = mid; 96 | break; 97 | }else{ 98 | l = mid + 1; 99 | } 100 | } 101 | } 102 | System.out.println(low + " " + high); 103 | } 104 | } 105 | } 106 | } 107 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing790数的三次方根.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 790. 数的三次方根 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个浮点数n,求它的三次方根。 10 | 11 | **输入格式** 12 | 13 | 共一行,包含一个浮点数n。 14 | 15 | **输出格式** 16 | 17 | 共一行,包含一个浮点数,表示问题的解。 18 | 19 | 注意,结果保留6位小数。 20 | 21 | **数据范围**: 22 | ```r 23 | −10000≤n≤10000 24 | ``` 25 | **输入样例**: 26 | 27 | ```r 28 | 1000.00 29 | ``` 30 | 31 | **输出样例**: 32 | 33 | ```r 34 | 10.000000 35 | ``` 36 | 37 | ### Solution 38 | 39 | ```java 40 | import java.io.*; 41 | 42 | public class Main{ 43 | public static void main(String[] args) throws IOException{ 44 | final double ACC = 1e-8; 45 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 46 | double x = Double.parseDouble(in.readLine()); 47 | double l = -100, r = 100; 48 | while(r - l > ACC){ 49 | double mid = (l + r) / 2; 50 | if(mid * mid * mid > x) r = mid; 51 | else l = mid; 52 | } 53 | System.out.printf("%.6f", l); 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing791高精度加法.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 791. 高精度加法 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个正整数,计算它们的和 10 | 11 | **输入格式** 12 | 13 | 共两行,每行包含一个整数。 14 | 15 | **输出格式** 16 | 17 | 共一行,包含所求的和。 18 | 19 | **数据范围**: 20 | ```r 21 | 1≤整数长度≤100000 22 | ``` 23 | **输入样例**: 24 | 25 | ```r 26 | 12 27 | 23 28 | ``` 29 | 30 | **输出样例**: 31 | 32 | ```r 33 | 35 34 | ``` 35 | 36 | ### Solution 37 | 38 | 1. 用BitInteger来处理 39 | 40 | ```java 41 | import java.math.BigInteger; 42 | import java.io.*; 43 | 44 | public class Main { 45 | 46 | public static void main(String[] args) throws IOException { 47 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 48 | BigInteger a = new BigInteger(in.readLine()); 49 | BigInteger b = new BigInteger(in.readLine()); 50 | System.out.println(a.add(b)); 51 | 52 | } 53 | 54 | } 55 | ``` 56 | 57 | 2. 用字符串形式来,会比BigInteger的时间快一倍 58 | 59 | ```java 60 | import java.util.*; 61 | import java.io.*; 62 | 63 | public class Main{ 64 | public static void main(String[] args) throws IOException{ 65 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 66 | // 字符串读取两个数字 67 | String a = in.readLine(), b = in.readLine(); 68 | // 用数组保存数字,数组的0位对应数字的个位,所以倒序遍历字符串 69 | List x = new ArrayList<>(); 70 | List y = new ArrayList<>(); 71 | for(int i = a.length() - 1; i >= 0; i--) x.add(a.charAt(i) - '0'); 72 | for(int i = b.length() - 1; i >= 0; i--) y.add(b.charAt(i) - '0'); 73 | // 用栈来存储,存的话先存个位,打印的话先打印高位 74 | Deque c = new ArrayDeque<>(); 75 | c = add(x, y); 76 | while(!c.isEmpty()){ 77 | System.out.print(c.pop()); 78 | } 79 | } 80 | // 人为规定 x > y 81 | public static Deque add(List x, List y){ 82 | if(x.size() < y.size()) return add(y, x); 83 | Deque c =new ArrayDeque<>(); 84 | // t 记录进位 85 | int t = 0; 86 | for(int i = 0; i < x.size(); i++){ 87 | t += x.get(i); 88 | if(i < y.size()) t += y.get(i); 89 | c.push(t % 10); 90 | t = t / 10; 91 | } 92 | // 最后还有一个进位,别忽略 93 | if(t > 0) c.push(t); 94 | return c; 95 | } 96 | } 97 | ``` 98 | 99 | 3. 用数组存的方法,感觉比 List 灵活一点 100 | 101 | ``` 102 | import java.util.*; 103 | 104 | class Main{ 105 | public static void main(String[] args){ 106 | Scanner sc = new Scanner(System.in); 107 | String sa = sc.next(); 108 | String sb = sc.next(); 109 | int la = sa.length(); 110 | int lb = sb.length(); 111 | int[] a = new int[la]; 112 | int[] b = new int[lb]; 113 | List c = new ArrayList<>(); 114 | for(int i = la - 1, k = 0; i >= 0; i--, k++){ 115 | a[k] = sa.charAt(i) - '0'; 116 | } 117 | for(int i = lb - 1, k = 0; i >= 0; i--, k++){ 118 | b[k] = sb.charAt(i) - '0'; 119 | } 120 | // 加法中 a 的长度 大于等于 b 的长度 121 | if(la > lb) c = add(a, b); 122 | else c = add(b, a); 123 | for(int i = c.size() - 1; i >= 0; i--){ 124 | System.out.print(c.get(i)); 125 | } 126 | 127 | } 128 | public static List add(int[] a, int[] b){ 129 | List c = new ArrayList<>(); 130 | // 加法,只需要记录进位 131 | int t = 0; 132 | for(int i = 0; i < a.length; i++){ 133 | t += a[i]; 134 | if(i < b.length) t += b[i]; 135 | c.add(t % 10); 136 | t /= 10; 137 | } 138 | // 最后以防还有进位 139 | if(t > 0) c.add(t); 140 | return c; 141 | } 142 | } 143 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing792高精度减法.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 792. 高精度减法 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个正整数,计算它们的差,计算结果可能为负数。 10 | 11 | **输入格式** 12 | 13 | 共两行,每行包含一个整数。 14 | 15 | **输出格式** 16 | 17 | 共一行,包含所求的差。 18 | 19 | ```r 20 | 数据范围 21 | 22 | 1≤整数长度≤100000 23 | 24 | 输入样例: 25 | 26 | 32 27 | 11 28 | 29 | 输出样例: 30 | 31 | 21 32 | ``` 33 | ### Solution 34 | 35 | ```java 36 | import java.util.*; 37 | import java.io.*; 38 | 39 | public class Main{ 40 | public static void main(String[] args) throws IOException{ 41 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 42 | // 字符串读取两个数字 43 | String a = in.readLine(), b = in.readLine(); 44 | // 用数组保存数字,数组的0位对应数字的个位,所以倒序遍历字符串 45 | List x = new ArrayList<>(); 46 | List y = new ArrayList<>(); 47 | for(int i = a.length() - 1; i >= 0; i--) x.add(a.charAt(i) - '0'); 48 | for(int i = b.length() - 1; i >= 0; i--) y.add(b.charAt(i) - '0'); 49 | // 用栈来存储,存的话先存个位,打印的话先打印高位 50 | Deque c = new ArrayDeque<>(); 51 | // 如果 x 大于等于 y, 就用 x - y 52 | // 如果 x 小于 y,先打印负号,再计算 y - x 53 | if(cmp(x, y)) c = sub(x, y); 54 | else{ 55 | System.out.print("-"); 56 | c = sub(y, x); 57 | } 58 | while(!c.isEmpty()){ 59 | System.out.print(c.pop()); 60 | } 61 | } 62 | public static boolean cmp(List x, List y){ 63 | if(x.size() != y.size()) return x.size() > y.size(); 64 | for(int i = x.size() - 1; i >= 0; i--){ 65 | if(x.get(i) != y.get(i)) return x.get(i) > y.get(i); 66 | } 67 | return true; 68 | } 69 | public static Deque sub(List x, List y){ 70 | Deque c =new ArrayDeque<>(); 71 | // t 记录借位 72 | int t = 0; 73 | for(int i = 0; i < x.size(); i++){ 74 | t = x.get(i) - t; 75 | if(i < y.size()) t = t - y.get(i); 76 | c.push((t + 10) % 10); 77 | // 如果 t 为负数,就借一位,否则不用借位 78 | t = t < 0 ? 1 : 0; 79 | } 80 | // 去除前导 0 81 | while( c.size() > 1 && c.peek() == 0) c.pop(); 82 | return c; 83 | } 84 | } 85 | ``` 86 | 87 | 数组代替 List 表示被减数和减数 88 | 89 | ```java 90 | import java.util.*; 91 | 92 | class Main{ 93 | public static void main(String[] args){ 94 | Scanner sc = new Scanner(System.in); 95 | String sa = sc.next(); 96 | String sb = sc.next(); 97 | int la = sa.length(); 98 | int lb = sb.length(); 99 | int[] a = new int[la]; 100 | int[] b = new int[lb]; 101 | for(int i = la - 1, j = 0; i >= 0; i--, j++) a[j] = sa.charAt(i) - '0'; 102 | for(int i = lb - 1, j = 0; i >= 0; i--, j++) b[j] = sb.charAt(i) - '0'; 103 | // 比较两个数,用大的数减去小的数 104 | Deque c = new ArrayDeque<>(); 105 | if(cmp(a, b)) c = sub(a, b); 106 | else{ 107 | System.out.print("-"); 108 | c = sub(b, a); 109 | } 110 | while(!c.isEmpty()) System.out.print(c.pop()); 111 | } 112 | public static boolean cmp(int[] a, int[] b){ 113 | if(a.length != b.length) return a.length > b.length; 114 | else{ 115 | for(int i = a.length - 1; i >= 0; i--){ 116 | if(a[i] != b[i]) return a[i] > b[i]; 117 | } 118 | } 119 | return true; 120 | } 121 | public static Deque sub(int[] a, int[] b){ 122 | Deque c = new ArrayDeque<>(); 123 | // t 保存借位 124 | int t = 0; 125 | for(int i = 0; i < a.length; i++){ 126 | t = a[i] - t; 127 | if(i < b.length) t -= b[i]; 128 | // 加10模10,很好的处理了负数的 t 129 | c.push((t + 10) % 10); 130 | if(t < 0) t = 1; 131 | else t = 0; 132 | } 133 | // 相减可能有 0,去除前导 0 134 | // 但是要保留最后一位 0 135 | while(c.size() > 1 && c.peek() == 0) c.pop(); 136 | return c; 137 | } 138 | } 139 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing793高精度乘法.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 793. 高精度乘法 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个正整数A和B,请你计算A * B的值。 10 | 11 | **输入格式** 12 | 13 | 共两行,第一行包含整数A,第二行包含整数B。 14 | 15 | **输出格式** 16 | 17 | 共一行,包含A * B的值。 18 | 19 | 20 | 数据范围 21 | 22 | $1≤A的长度≤100000,$ 23 | $0≤B≤10000$ 24 | 25 | ```r 26 | 输入样例: 27 | 28 | 2 29 | 3 30 | 31 | 输出样例: 32 | 33 | 6 34 | ``` 35 | ### Solution 36 | 37 | ```java 38 | import java.util.*; 39 | 40 | class Main{ 41 | public static void main(String[] args) { 42 | Scanner sc = new Scanner(System.in); 43 | String sa = sc.next(); 44 | int la = sa.length(); 45 | int[] a = new int[la]; 46 | // b 比较小,可以用 int 存 47 | int b = sc.nextInt(); 48 | for(int i = la - 1, j = 0; i >= 0; i--, j++) a[j] = sa.charAt(i) - '0'; 49 | Deque c = new ArrayDeque<>(); 50 | mul(a, b, c); 51 | while(!c.isEmpty()) System.out.print(c.pop()); 52 | } 53 | public static void mul(int[] a, int b, Deque c){ 54 | int t = 0; 55 | for(int i = 0; i < a.length; i++){ 56 | t = t + a[i] * b; 57 | c.push(t % 10); 58 | t = t / 10; 59 | } 60 | // 因为 t 可能很大,所以也要把 t 加到 c 中 61 | while(t > 0){ 62 | c.push(t % 10); 63 | t /= 10; 64 | } 65 | // 以防是 0 去除前导 0 66 | while(c.size() > 1 && c.peek() == 0) c.pop(); 67 | } 68 | } 69 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing794高精度除法.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 794. 高精度除法 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个非负整数A,B,请你计算 A / B的商和余数。 10 | 11 | **输入格式** 12 | 13 | 共两行,第一行包含整数A,第二行包含整数B。 14 | 15 | **输出格式** 16 | 17 | 共两行,第一行输出所求的商,第二行输出所求余数。 18 | 19 | 20 | 数据范围 21 | 22 | $1≤A的长度≤100000,$ 23 | $0≤B≤10000$ 24 | 25 | ```r 26 | 输入样例: 27 | 28 | 7 29 | 2 30 | 31 | 输出样例: 32 | 33 | 3 34 | 1 35 | ``` 36 | ### Solution 37 | 38 | ```java 39 | import java.util.*; 40 | 41 | class Main{ 42 | public static void main(String[] args){ 43 | Scanner sc = new Scanner(System.in); 44 | String sa = sc.next(); 45 | int la = sa.length(); 46 | int[] a = new int[la]; 47 | for(int i = 0, j = 0; i < la; i++, j++) a[i] = sa.charAt(j) - '0'; 48 | int b = sc.nextInt(); 49 | Deque c = new ArrayDeque<>(); 50 | // 商用 c 存储,返回余数 51 | int t = div(a, b, c); 52 | while(!c.isEmpty()) System.out.print(c.pollFirst()); 53 | System.out.print("\n" + t); 54 | } 55 | public static int div(int[] a, int b, Deque c){ 56 | int t = 0; 57 | for(int i = 0; i < a.length; i++){ 58 | t = t * 10 + a[i]; 59 | c.addLast(t / b); 60 | t = t % b; 61 | } 62 | while(c.size() > 1 && c.peekFirst() == 0) c.pollFirst(); 63 | return t; 64 | } 65 | } 66 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing795前缀和.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 795. 前缀和 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 输入一个长度为n的整数序列。 10 | 11 | 接下来再输入m个询问,每个询问输入一对l, r。 12 | 13 | 对于每个询问,输出原序列中从第l个数到第r个数的和。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m。 18 | 19 | 第二行包含n个整数,表示整数数列。 20 | 21 | 接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。 22 | 23 | **输出格式** 24 | 25 | 共m行,每行输出一个询问的结果。 26 | 27 | ```r 28 | 数据范围 29 | 30 | 1≤l≤r≤n, 31 | 1≤n,m≤100000, 32 | −1000≤数列中元素的值≤1000 33 | 34 | 输入样例: 35 | 36 | 5 3 37 | 2 1 3 6 4 38 | 1 2 39 | 1 3 40 | 2 4 41 | 42 | 输出样例: 43 | 44 | 3 45 | 6 46 | 10 47 | ``` 48 | 49 | ### Solution 50 | 51 | ```java 52 | import java.util.*; 53 | import java.io.*; 54 | 55 | public class Main{ 56 | public static void main(String[] args) throws IOException{ 57 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 58 | String[] s = in.readLine().split(" "); 59 | int n = Integer.parseInt(s[0]); 60 | int m = Integer.parseInt(s[1]); 61 | s = in.readLine().split(" "); 62 | int[] a = new int[n]; 63 | int[] p = new int[n + 1]; 64 | // 计算前缀和:前i个元素的前缀和 65 | // 下标领先一个数组小标,为了表示出前 0 个前缀和为 0 66 | for(int i = 0; i < n; i++){ 67 | a[i] = Integer.parseInt(s[i]); 68 | p[i + 1] = p[i] + a[i]; 69 | } 70 | while(m>0){ 71 | m--; 72 | s = in.readLine().split(" "); 73 | int l = Integer.parseInt(s[0]); 74 | int r = Integer.parseInt(s[1]); 75 | System.out.println(p[r] - p[l - 1]); 76 | } 77 | } 78 | } 79 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing796子矩阵的和.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 796. 子矩阵的和 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。 10 | 11 | 对于每个询问输出子矩阵中所有数的和。 12 | 13 | **输入格式** 14 | 15 | 第一行包含三个整数n,m,q。 16 | 17 | 接下来n行,每行包含m个整数,表示整数矩阵。 18 | 19 | 接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。 20 | 21 | **输出格式** 22 | 23 | 共q行,每行输出一个询问的结果。 24 | 25 | ```r 26 | 数据范围 27 | 28 | 1≤n,m≤1000, 29 | 1≤q≤200000, 30 | 1≤x1≤x2≤n, 31 | 1≤y1≤y2≤m, 32 | −1000≤矩阵内元素的值≤1000 33 | 34 | 输入样例: 35 | 36 | 3 4 3 37 | 1 7 2 4 38 | 3 6 2 8 39 | 2 1 2 3 40 | 1 1 2 2 41 | 2 1 3 4 42 | 1 3 3 4 43 | 44 | 输出样例: 45 | 46 | 17 47 | 27 48 | 21 49 | ``` 50 | 51 | ### Solution 52 | 53 | ```java 54 | import java.util.*; 55 | import java.io.*; 56 | 57 | public class Main{ 58 | public static void main(String[] args) throws IOException{ 59 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 60 | String[] s = in.readLine().split(" "); 61 | int n = Integer.parseInt(s[0]); 62 | int m = Integer.parseInt(s[1]); 63 | int q = Integer.parseInt(s[2]); 64 | int[][] a = new int[n][m]; 65 | int[][] p = new int[n + 1][m + 1]; 66 | for(int i = 0; i < n; i++){ 67 | s = in.readLine().split(" "); 68 | for(int j = 0; j < m; j++){ 69 | a[i][j] = Integer.parseInt(s[j]); 70 | p[i + 1][j + 1] = a[i][j] + p[i][j + 1] + p[i + 1][j] - p[i][j]; 71 | } 72 | } 73 | while(q > 0){ 74 | q--; 75 | s = in.readLine().split(" "); 76 | int x1 = Integer.parseInt(s[0]); 77 | int y1 = Integer.parseInt(s[1]); 78 | int x2 = Integer.parseInt(s[2]); 79 | int y2 = Integer.parseInt(s[3]); 80 | System.out.println(p[x2][y2] - p[x2][y1 - 1] - p[x1 - 1][y2] + p[x1 - 1][y1 - 1]); 81 | } 82 | } 83 | } 84 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing797差分.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 797. 差分 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 输入一个长度为n的整数序列。 10 | 11 | 接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。 12 | 13 | 请你输出进行完所有操作后的序列。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m。 18 | 19 | 第二行包含n个整数,表示整数序列。 20 | 21 | 接下来m行,每行包含三个整数l,r,c,表示一个操作。 22 | 23 | **输出格式** 24 | 25 | 共一行,包含n个整数,表示最终序列。 26 | 27 | 28 | ```r 29 | 数据范围 30 | 31 | 1≤n,m≤100000, 32 | 1≤l≤r≤n, 33 | −1000≤c≤1000, 34 | −1000≤整数序列中元素的值≤1000 35 | 36 | 输入样例: 37 | 38 | 6 3 39 | 1 2 2 1 2 1 40 | 1 3 1 41 | 3 5 1 42 | 1 6 1 43 | 44 | 输出样例: 45 | 46 | 3 4 5 3 4 2 47 | ``` 48 | 49 | ### Solution 50 | 51 | ```java 52 | import java.util.*; 53 | import java.io.*; 54 | 55 | public class Main{ 56 | public static void main(String[] args) throws IOException{ 57 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 58 | String[] s = in.readLine().split(" "); 59 | // 整数序列 n 个整数 60 | int n = Integer.parseInt(s[0]); 61 | // m 个插入操作 62 | // 插入操作对于数组的索引是从 1 开始的 63 | // 所以建立差分数组也是从从 1 开始,比较不容易出错 64 | int m = Integer.parseInt(s[1]); 65 | s = in.readLine().split(" "); 66 | int[] a = new int[n + 10]; 67 | for(int i = 1; i <= n; i++){ 68 | int c = Integer.parseInt(s[i - 1]); 69 | // 插入 c 70 | insert(a, i, i, c); 71 | } 72 | while(m > 0){ 73 | m--; 74 | s = in.readLine().split(" "); 75 | int l = Integer.parseInt(s[0]); 76 | int r = Integer.parseInt(s[1]); 77 | int c = Integer.parseInt(s[2]); 78 | insert(a, l, r, c); 79 | } 80 | for(int i = 1; i <= n; i++){ 81 | a[i] += a[i - 1]; 82 | System.out.print(a[i] + " "); 83 | } 84 | 85 | } 86 | 87 | public static void insert(int[] a, int l, int r, int c){ 88 | a[l] += c; 89 | a[r + 1] -= c; 90 | } 91 | 92 | } 93 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing798差分矩阵.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 798. 差分矩阵 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。 10 | 11 | 每个操作都要将选中的子矩阵中的每个元素的值加上c。 12 | 13 | 请你将进行完所有操作后的矩阵输出。 14 | 15 | **输入格式** 16 | 17 | 第一行包含整数n,m,q。 18 | 19 | 接下来n行,每行包含m个整数,表示整数矩阵。 20 | 21 | 接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。 22 | 23 | **输出格式** 24 | 25 | 共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。 26 | 27 | ```r 28 | 数据范围 29 | 30 | 1≤n,m≤1000, 31 | 1≤q≤100000, 32 | 1≤x1≤x2≤n, 33 | 1≤y1≤y2≤m, 34 | −1000≤c≤1000, 35 | −1000≤矩阵内元素的值≤1000 36 | 37 | 输入样例: 38 | 39 | 3 4 3 40 | 1 2 2 1 41 | 3 2 2 1 42 | 1 1 1 1 43 | 1 1 2 2 1 44 | 1 3 2 3 2 45 | 3 1 3 4 1 46 | 47 | 输出样例: 48 | 49 | 2 3 4 1 50 | 4 3 4 1 51 | 2 2 2 2 52 | ``` 53 | 54 | ### Solution 55 | 56 | ```java 57 | import java.util.*; 58 | import java.io.*; 59 | 60 | public class Main{ 61 | public static void main(String[] args) throws IOException{ 62 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 63 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 64 | String[] s = in.readLine().split(" "); 65 | int n = Integer.parseInt(s[0]); 66 | int m = Integer.parseInt(s[1]); 67 | int q = Integer.parseInt(s[2]); 68 | int[][] a = new int[n + 10][m + 10]; 69 | int[][] b = new int[n + 10][m + 10]; 70 | for(int i = 1; i <= n; i++){ 71 | s = in.readLine().split(" "); 72 | for(int j = 1; j <= m; j++){ 73 | int c = Integer.parseInt(s[j - 1].trim()); 74 | insert(a, i, j, i, j, c); 75 | } 76 | } 77 | while(q > 0){ 78 | q--; 79 | s = in.readLine().split(" "); 80 | int x1 = Integer.parseInt(s[0]); 81 | int y1 = Integer.parseInt(s[1]); 82 | int x2 = Integer.parseInt(s[2]); 83 | int y2 = Integer.parseInt(s[3]); 84 | int c = Integer.parseInt(s[4]); 85 | insert(a, x1, y1, x2, y2, c); 86 | } 87 | for(int i = 1; i <= n; i++){ 88 | for(int j = 1; j <= m; j++){ 89 | b[i][j] = a[i][j] + b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]; 90 | // 如果不是以字符串的形式输出,就输出不了数字 91 | out.write(b[i][j] + " "); 92 | if(j == m){ 93 | out.write("\n"); 94 | } 95 | 96 | } 97 | out.flush(); 98 | 99 | } 100 | 101 | } 102 | public static void insert(int[][] a, int x1, int y1, int x2, int y2, int c){ 103 | a[x1][y1] += c; 104 | a[x2 + 1][y1] -= c; 105 | a[x1][y2 + 1] -= c; 106 | a[x2 + 1][y2 + 1] += c; 107 | } 108 | } 109 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing799最长连续不重复子序列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 799. 最长连续不重复子序列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 第二行包含n个整数(均在0~100000范围内),表示整数序列。 16 | 17 | **输出格式** 18 | 19 | 共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。 20 | 21 | ```r 22 | 数据范围 23 | 24 | 1≤n≤100000 25 | 26 | 输入样例: 27 | 28 | 5 29 | 1 2 2 3 5 30 | 31 | 输出样例: 32 | 33 | 3 34 | ``` 35 | 36 | ### Solution 37 | 38 | ```java 39 | import java.util.*; 40 | import java.io.*; 41 | 42 | public class Main{ 43 | public static void main(String[] args) throws IOException{ 44 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 45 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 46 | int n = Integer.parseInt(in.readLine()); 47 | String[] s = in.readLine().split(" "); 48 | int[] a = new int[n + 10]; 49 | for(int i = 0; i < n; i++){ 50 | a[i] = Integer.parseInt(s[i]); 51 | } 52 | int[] q = new int[100010]; 53 | int res = 0; 54 | // 区间 [i, j] 为不包含重复元素的区间 55 | // 遍历每一个元素,找到以该元素为结尾的最长不重复字符串 56 | // 当结尾元素往后移 57 | // 1. 如果出现了重复字符,开始元素往后移动 58 | // 2. 如果没有出现重复元素,开始元素不动 59 | // 可以发现开始元素的移动具有单调性 60 | // 这种场景适合双指针 61 | for(int i = 0, j = 0; j < n; j++){ 62 | q[a[j]]++; 63 | while(i < j && q[a[j]] > 1){ 64 | q[a[i]]--; 65 | i++; 66 | } 67 | res = Math.max(res, j - i + 1); 68 | } 69 | out.write(String.valueOf(res)); 70 | out.flush(); 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing800数组元素的目标和.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 800. 数组元素的目标和 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个升序排序的有序数组A和B,以及一个目标值x。数组下标从0开始。 10 | 请你求出满足A[i] + B[j] = x的数对(i, j)。 11 | 12 | 数据保证有唯一解。 13 | 14 | **输入格式** 15 | 16 | 第一行包含三个整数n,m,x,分别表示A的长度,B的长度以及目标值x。 17 | 18 | 第二行包含n个整数,表示数组A。 19 | 20 | 第三行包含m个整数,表示数组B。 21 | 22 | **输出格式** 23 | 24 | 共一行,包含两个整数 i 和 j。 25 | 26 | ```r 27 | 数据范围 28 | 29 | 数组长度不超过100000。 30 | 同一数组内元素各不相同。 31 | 1≤数组元素≤109 32 | 33 | 输入样例: 34 | 35 | 4 5 6 36 | 1 2 4 7 37 | 3 4 6 8 9 38 | 39 | 输出样例: 40 | 41 | 1 1 42 | ``` 43 | 44 | ### Solution 45 | 46 | ```java 47 | import java.util.*; 48 | import java.io.*; 49 | 50 | public class Main{ 51 | public static void main(String[] args) throws IOException{ 52 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 53 | String[] s = in.readLine().split(" "); 54 | int n = Integer.parseInt(s[0]); 55 | int m = Integer.parseInt(s[1]); 56 | int x = Integer.parseInt(s[2]); 57 | int[] a = new int[n + 10]; 58 | int[] b = new int[m + 10]; 59 | s = in.readLine().split(" "); 60 | for(int i = 0; i < n; i++){ 61 | a[i] = Integer.parseInt(s[i]); 62 | } 63 | s = in.readLine().split(" "); 64 | for(int i = 0; i < m; i++){ 65 | b[i] = Integer.parseInt(s[i]); 66 | } 67 | // A 数组正序遍历 68 | // B 数组倒序遍历 69 | for(int i = 0, j = m - 1; i < n; i++){ 70 | while(j > 0 && a[i] + b[j] > x) j--; 71 | if(a[i] + b[j] == x){ 72 | System.out.println(i + " " + j); 73 | } 74 | } 75 | 76 | } 77 | } 78 | ``` -------------------------------------------------------------------------------- /1基础算法/AcWing801二进制中1的个数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 801. 二进制中1的个数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为n的数列,请你求出数列中每个数的二进制表示中1的个数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 第二行包含n个整数,表示整个数列。 16 | 17 | **输出格式** 18 | 19 | 共一行,包含n个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中1的个数。 20 | 21 | ```r 22 | 数据范围 23 | 24 | 1≤n≤100000, 25 | 0≤数列中元素的值≤109 26 | 27 | 输入样例: 28 | 29 | 5 30 | 1 2 3 4 5 31 | 32 | 输出样例: 33 | 34 | 1 1 2 1 2 35 | ``` 36 | 37 | ### Solution 38 | 39 | 1. 求 n 的第 k 为数字: `n >> k & 1` 40 | 2. 返回 x 的最后一位 1:lowbit(x)原理:`x & -x`;x & -x 其实就是 x & (~x + 1)` 41 | 3. 补码为什么是 `~x + 1` 42 | ```r 43 | x + (-x) = 0 44 | -x = 0 - x 45 | -x = 000...000 - x 46 | -x = 1000...000 - x 47 | -x = ~x + 1 48 | ``` 49 | 50 | ```java 51 | import java.util.*; 52 | import java.io.*; 53 | 54 | public class Main{ 55 | public static void main(String[] args) throws IOException{ 56 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 57 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 58 | int n = Integer.parseInt(in.readLine()); 59 | String[] s = in.readLine().split(" "); 60 | 61 | for(int i = 0; i < n; i++){ 62 | int x = Integer.parseInt(s[i]); 63 | int res = 0; 64 | while(x > 0) { 65 | // lowbit 操作 66 | // x = x - (x & -x); 67 | t &= (t - 1); 68 | res++; 69 | } 70 | out.write(res + " "); 71 | out.flush(); 72 | // System.out.print(res + " "); 73 | } 74 | 75 | } 76 | } 77 | ``` 78 | 79 | ### yxc 80 | 81 | 1. lowbit操作 82 | 83 | ![image-20210217153826737](pics/image-20210217153826737.png) 84 | 85 | 2. 原码反码补码 86 | 87 | ![image-20210217154038552](pics/image-20210217154038552.png) -------------------------------------------------------------------------------- /1基础算法/AcWing802区间和.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 802. 区间和 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 假定有一个无限长的数轴,数轴上每个坐标上的数都是0。 10 | 11 | 现在,我们首先进行 n 次操作,每次操作将某一位置x上的数加c。 12 | 13 | 接下来,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m。 18 | 19 | 接下来 n 行,每行包含两个整数x和c。 20 | 21 | 再接下里 m 行,每行包含两个整数l和r。 22 | 23 | **输出格式** 24 | 25 | 共m行,每行输出一个询问中所求的区间内数字和。 26 | 27 | 数据范围 28 | 29 | $ −10^9≤x≤10^9,$ 30 | $ 1≤n,m≤10^5,$ 31 | $ −10^9≤l≤r≤10^9,$ 32 | $ −10000≤c≤10000$ 33 | ```r 34 | 输入样例: 35 | 36 | 3 3 37 | 1 2 38 | 3 6 39 | 7 5 40 | 1 3 41 | 4 6 42 | 7 8 43 | 44 | 输出样例: 45 | 46 | 8 47 | 0 48 | 5 49 | ``` 50 | 51 | ### Solution 52 | 53 | 1. 用题目中会用到的数字最多有 3 * 10^5,可以用来离散化表示 10^9 54 | 2. alls 存所有用到的下标,adds 存所有添加操作,querys 存所有查询操作 55 | 3. a 存执行添加操作之后的下标的值,q 存前缀和 56 | 57 | 用二维数组代替 Pairs,用数组代替 List,速度快了一倍 58 | 59 | ```java 60 | import java.util.*; 61 | import java.io.*; 62 | 63 | class Main{ 64 | static final int N = 100010; 65 | static int[] a = new int[3 * N]; 66 | static int[] q = new int[3 * N]; 67 | static int[][] adds = new int[N][2]; 68 | static int[][] querys = new int[N][2]; 69 | static int[] alls = new int[3 * N]; 70 | public static int unique(int[] alls, int n){ 71 | // 双指针 72 | int j = 0; 73 | for(int i = 0; i < n; i++){ 74 | if(i == 0 || alls[i] != alls[i - 1]){ 75 | alls[j] = alls[i]; 76 | j++; 77 | } 78 | } 79 | return j; 80 | } 81 | public static int bsearch(int[] alls, int n, int x){ 82 | int low = 0, high = n - 1; 83 | while(low <= high){ 84 | int mid = low + high >> 1; 85 | if(alls[mid] > x) high = mid - 1; 86 | else if(alls[mid] < x) low = mid + 1; 87 | else return mid + 1; 88 | } 89 | return low; 90 | } 91 | public static void main(String[] args) throws IOException{ 92 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 93 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 94 | String[] s = br.readLine().split(" "); 95 | int n = Integer.parseInt(s[0]); 96 | int m = Integer.parseInt(s[1]); 97 | // 下标 98 | int idx = 0, idx1 = 0; 99 | // 存插入 100 | for(int i = 0; i < n; i++){ 101 | s = br.readLine().split(" "); 102 | int x = Integer.parseInt(s[0]); 103 | int c = Integer.parseInt(s[1]); 104 | alls[idx++] = x; 105 | adds[i][0] = x; 106 | adds[i][1] = c; 107 | } 108 | for(int i = 0; i < m; i++){ 109 | s = br.readLine().split(" "); 110 | int l = Integer.parseInt(s[0]); 111 | int r = Integer.parseInt(s[1]); 112 | alls[idx++] = l; 113 | alls[idx++] = r; 114 | querys[i][0] = l; 115 | querys[i][1] = r; 116 | } 117 | // alls(0, idx - 1) 排序 118 | Arrays.sort(alls, 0, idx); 119 | // 去重 120 | int end = unique(alls, idx); 121 | // 添加操作 122 | for(int i = 0; i < n; i++){ 123 | // 二分查找找到 x 在 alls 数组中的下标 124 | int t = bsearch(alls, end, adds[i][0]); 125 | a[t] += adds[i][1]; 126 | } 127 | // 计算前缀和 128 | for(int i = 1; i <= end; i++){ 129 | q[i] = q[i - 1] + a[i]; 130 | } 131 | for(int i = 0; i < m; i++){ 132 | int l = bsearch(alls, end, querys[i][0]); 133 | int r = bsearch(alls, end, querys[i][1]); 134 | bw.write(q[r] - q[l - 1] + "\n"); 135 | } 136 | bw.close(); 137 | br.close(); 138 | 139 | } 140 | } 141 | ``` 142 | 143 | 用了 List 和 Pairs,效率不高 144 | 145 | ```java 146 | import java.util.*; 147 | import java.io.*; 148 | 149 | public class Main{ 150 | static class Pairs{ 151 | int first, second; 152 | public Pairs(int first, int second){ 153 | this.first = first; 154 | this.second = second; 155 | } 156 | } 157 | public static void main(String[] args) throws IOException{ 158 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 159 | BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 160 | String[] s = in.readLine().split(" "); 161 | int n = Integer.parseInt(s[0]); 162 | int m = Integer.parseInt(s[1]); 163 | // 数据范围为 10 的 9 次方,如果直接一个数组来存,空间就会超出限制 164 | // 但是操作数和查询数只有 10 的 5 次方 165 | // 想到用离散化的方式,用 10 的 5 次方的长度表示 10 的 9 次方的量级 166 | // 申请的数组长度 n + m + m + 10 就可以,加个 10 防止边界问题 167 | int N = n + m + m + 10; 168 | // a[] 去重,离散化之后的数组 169 | int[] a = new int[N]; 170 | // p[] a的前缀和 171 | int[] p = new int[N]; 172 | // adds 记录添加操作 173 | List adds = new ArrayList<>(); 174 | // querys 记录查询操作 175 | List querys = new ArrayList<>(); 176 | // alls 记录所有在数轴上出现过的坐标 177 | List alls = new ArrayList<>(); 178 | for(int i = 0; i < n; i++){ 179 | s = in.readLine().split(" "); 180 | int x = Integer.parseInt(s[0]); 181 | int c = Integer.parseInt(s[1]); 182 | adds.add(new Pairs(x, c)); 183 | alls.add(x); 184 | } 185 | 186 | for(int i = 0; i < m; i++){ 187 | s = in.readLine().split(" "); 188 | int l = Integer.parseInt(s[0]); 189 | int r = Integer.parseInt(s[1]); 190 | 191 | querys.add(new Pairs(l, r)); 192 | alls.add(l); 193 | alls.add(r); 194 | } 195 | // 排序 196 | Collections.sort(alls); 197 | // 去重 198 | int end = unique(alls); 199 | alls = alls.subList(0, end); 200 | 201 | // 离散化,就是找到要插入或者查询的数字在 alls 的位置 202 | // 可以用二分查找 203 | // 插入 204 | // 返回值以 1 开始计数 205 | for(int i = 0; i < n; i++){ 206 | int index = bsearch(adds.get(i).first, alls); 207 | a[index] += adds.get(i).second; 208 | } 209 | 210 | // 计算前缀和 211 | for(int i = 1; i < N; i++){ 212 | p[i] = p[i - 1] + a[i]; 213 | } 214 | 215 | // 开始查询输出 216 | for(int i = 0; i < m; i++){ 217 | int l = bsearch(querys.get(i).first, alls); 218 | int r = bsearch(querys.get(i).second, alls); 219 | int res = p[r] - p[l - 1]; 220 | out.write(res + "\n"); 221 | out.flush(); 222 | } 223 | } 224 | public static int unique(List list){ 225 | int j = 0; 226 | for(int i = 0; i < list.size(); i++){ 227 | if(i == 0 || list.get(i) != list.get(i - 1)){ 228 | list.set(j, list.get(i)); 229 | j++; 230 | } 231 | } 232 | return j; 233 | } 234 | public static int bsearch(int x, List list){ 235 | int l = 0, r = list.size() - 1; 236 | while(l <= r){ 237 | int mid = l + r >> 1; 238 | if(list.get(mid) > x) r = mid - 1; 239 | else if(list.get(mid) < x) l = mid + 1; 240 | else return mid + 1; 241 | } 242 | return 1; 243 | } 244 | } 245 | ``` 246 | 247 | ### yxc 248 | 249 | 1. 离散化 250 | 251 | ![image-20210217155111008](pics/image-20210217155111008.png) 252 | 253 | -------------------------------------------------------------------------------- /1基础算法/AcWing803区间合并.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 803. 区间合并 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定 n 个区间 $[l_i,r_i]$,要求合并所有有交集的区间。 10 | 11 | 注意如果在端点处相交,也算有交集。 12 | 13 | 输出合并完成后的区间个数。 14 | 15 | 例如:[1,3]和[2,6]可以合并为一个区间[1,6]。 16 | 17 | **输入格式** 18 | 19 | 第一行包含整数n。 20 | 21 | 接下来n行,每行包含两个整数 l 和 r。 22 | 23 | **输出格式** 24 | 25 | 共一行,包含一个整数,表示合并区间完成后的区间个数。 26 | 27 | **数据范围**: 28 | 29 | $1≤n≤100000,$ 30 | $−10^9≤l_i≤r_i≤10^9$ 31 | 32 | ```r 33 | 输入样例: 34 | 35 | 5 36 | 1 2 37 | 2 4 38 | 5 6 39 | 7 8 40 | 7 9 41 | 42 | 输出样例: 43 | 44 | 3 45 | ``` 46 | 47 | ### Solution 48 | 49 | ```java 50 | import java.util.*; 51 | import java.io.*; 52 | 53 | public class Main{ 54 | public static class Pairs{ 55 | int l, r; 56 | public Pairs(int l, int r){ 57 | this.l = l; 58 | this.r = r; 59 | } 60 | } 61 | public static void main(String[] args) throws IOException{ 62 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 63 | int n = Integer.parseInt(in.readLine()); 64 | List alls = new ArrayList<>(); 65 | for(int i = 0; i < n; i++){ 66 | String[] s = in.readLine().split(" "); 67 | int l = Integer.parseInt(s[0]); 68 | int r = Integer.parseInt(s[1]); 69 | alls.add(new Pairs(l, r)); 70 | } 71 | // 利用 lambda 表达式重写比较器 72 | // 根据 左边界 升序排列 73 | Collections.sort(alls, (Pairs a1, Pairs a2) -> a1.l - a2.l); 74 | int res = 1; 75 | Pairs p = new Pairs(alls.get(0).l, alls.get(0).r); 76 | for(int i = 1; i < n; i++){ 77 | int l = alls.get(i).l; 78 | int r = alls.get(i).r; 79 | if(l > p.r){ 80 | res++; 81 | p.l = alls.get(i).l; 82 | p.r = alls.get(i).r; 83 | } 84 | else{ 85 | if(r > p.r){ 86 | p.r = alls.get(i).r; 87 | } 88 | } 89 | } 90 | System.out.println(res); 91 | } 92 | } 93 | ``` 94 | 95 | 1. 根据左端点排序 96 | 2. 更新右端点,取较大值`r = Math.max(a[i][1], r);` 97 | 3. 不用 List,Pair,只用数组 98 | 99 | ```java 100 | import java.util.*; 101 | import java.io.*; 102 | 103 | class Main{ 104 | static final int N = 100010; 105 | static int[][] a = new int[N][2]; 106 | public static void main(String[] args) throws IOException{ 107 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 108 | String[] s = br.readLine().split(" "); 109 | int n = Integer.parseInt(s[0]); 110 | for(int i = 0; i < n; i++){ 111 | s = br.readLine().split(" "); 112 | a[i][0] = Integer.parseInt(s[0]); 113 | a[i][1] = Integer.parseInt(s[1]); 114 | } 115 | // 按照左端点排序 116 | // 二维数组根据第一项升序,指定区间[0, n) 117 | Arrays.sort(a, 0, n, (x1, x2) -> x1[0] - x2[0]); 118 | // res 初始化为 1,之后每多一个区间就 +1 119 | int res = 1; 120 | int l = a[0][0], r = a[0][1]; 121 | for(int i = 1; i < n; i++){ 122 | // 上一个时刻的 r 小于当前的 l,说明不能合并 123 | if(r < a[i][0]){ 124 | res++; 125 | } 126 | // 更新 r,选两者较大值 127 | r = Math.max(a[i][1], r); 128 | } 129 | System.out.println(res); 130 | } 131 | } 132 | ``` -------------------------------------------------------------------------------- /1基础算法/pics/image-20210217152344632.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/1基础算法/pics/image-20210217152344632.png -------------------------------------------------------------------------------- /1基础算法/pics/image-20210217153826737.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/1基础算法/pics/image-20210217153826737.png -------------------------------------------------------------------------------- /1基础算法/pics/image-20210217154038552.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/1基础算法/pics/image-20210217154038552.png -------------------------------------------------------------------------------- /1基础算法/pics/image-20210217155111008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/1基础算法/pics/image-20210217155111008.png -------------------------------------------------------------------------------- /1基础算法/standard.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 基础算法 2 | 3 | ## AcWing 4 | 5 | `难度:` 6 | 7 | ### 题目描述 8 | 9 | 10 | ### Solution 11 | -------------------------------------------------------------------------------- /2数据结构/AcWing143最大异或对.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 143. 最大异或对 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 在给定的N个整数A1,A2……AN 10 | 11 | 中选出两个进行xor(异或)运算,得到的结果最大是多少? 12 | 13 | **输入格式** 14 | 15 | 第一行输入一个整数N。 16 | 17 | 第二行输入N个整数A1~AN。 18 | 19 | **输出格式** 20 | 21 | 输出一个整数表示答案。 22 | 23 | **数据范围** 24 | 25 | $1≤N≤10^5,$ 26 | $0≤Ai<2^31$ 27 | 28 | ```r 29 | 输入样例: 30 | 31 | 3 32 | 1 2 3 33 | 34 | 输出样例: 35 | 36 | 3 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | import java.io.*; 44 | 45 | class Main{ 46 | static final int N = 100010; 47 | static final int M = 31 * N; 48 | static int[][] son = new int[M][2]; 49 | static int[] a = new int[N]; 50 | static int idx = 0; 51 | public static void insert(int x){ 52 | int p = 0; 53 | for(int i = 30; i >= 0; i--){ 54 | int t = x >> i & 1; 55 | if(son[p][t] == 0) son[p][t] = ++idx; 56 | p = son[p][t]; 57 | } 58 | } 59 | public static int query(int x){ 60 | int res = 0; 61 | int p = 0; 62 | for(int i = 30; i >= 0; i--){ 63 | int t = x >> i & 1; 64 | if(son[p][t ^ 1] != 0) { 65 | p = son[p][t ^ 1]; 66 | res = res * 2 + (t ^ 1); 67 | } 68 | else { 69 | p = son[p][t]; 70 | res = res * 2 + t; 71 | } 72 | } 73 | return res; 74 | } 75 | public static void main(String[] args) throws IOException{ 76 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 77 | String[] s = br.readLine().split(" "); 78 | int n = Integer.parseInt(s[0]); 79 | s = br.readLine().split(" "); 80 | for(int i = 0; i < n; i++) a[i] = Integer.parseInt(s[i]); 81 | int res = 0; 82 | for(int i = 0; i < n; i++){ 83 | insert(a[i]); 84 | int t = query(a[i]); 85 | res = Math.max(res, a[i] ^ t); 86 | } 87 | System.out.println(res); 88 | } 89 | } 90 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing154滑动窗口.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 154. 滑动窗口 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个大小为 $n≤10^6$ 10 | 11 | 的数组。 12 | 13 | 有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。 14 | 15 | 您只能在窗口中看到k个数字。 16 | 17 | 每次滑动窗口向右移动一个位置。 18 | 19 | 以下是一个例子: 20 | 21 | 该数组为[1 3 -1 -3 5 3 6 7],k为3。 22 | 23 | ```r 24 | 窗口位置 最小值 最大值 25 | [1 3 -1] -3 5 3 6 7 -1 3 26 | 1 [3 -1 -3] 5 3 6 7 -3 3 27 | 1 3 [-1 -3 5] 3 6 7 -3 5 28 | 1 3 -1 [-3 5 3] 6 7 -3 5 29 | 1 3 -1 -3 [5 3 6] 7 3 6 30 | 1 3 -1 -3 5 [3 6 7] 3 7 31 | ``` 32 | 33 | 您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。 34 | 35 | **输入格式** 36 | 37 | 输入包含两行。 38 | 39 | 第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度。 40 | 41 | 第二行有n个整数,代表数组的具体数值。 42 | 43 | 同行数据之间用空格隔开。 44 | 45 | **输出格式** 46 | 47 | 输出包含两个。 48 | 49 | 第一行输出,从左至右,每个位置滑动窗口中的最小值。 50 | 51 | 第二行输出,从左至右,每个位置滑动窗口中的最大值。 52 | 53 | ```r 54 | 输入样例: 55 | 56 | 8 3 57 | 1 3 -1 -3 5 3 6 7 58 | 59 | 输出样例: 60 | 61 | -1 -3 -3 -3 3 3 62 | 3 3 5 5 6 7 63 | ``` 64 | 65 | ### Solution 66 | 67 | ```java 68 | import java.util.*; 69 | import java.io.*; 70 | 71 | public class Main{ 72 | public static void main(String[] args) throws IOException{ 73 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 74 | String[] s = in.readLine().split(" "); 75 | int n = Integer.parseInt(s[0]); 76 | int k = Integer.parseInt(s[1]); 77 | // 数组 a 保存原始数据 78 | int[] a = new int[n + 10]; 79 | // 数组 q 保存每一个滑动窗口处理之后的严格单调队列元素的下标 80 | // 注意 保存的是下标 81 | int[] q = new int[n + 10]; 82 | s = in.readLine().split(" "); 83 | for(int i = 0; i < n; i++){ 84 | a[i] = Integer.parseInt(s[i]); 85 | } 86 | // 1. 找滑动窗口的最小值 87 | // 滑动窗口的队列:队尾插入,队首弹出 88 | // hh 代表队首,tt 代表队尾 89 | // 分别初始化为 0 和 -1,保证滑动窗口为空的时候 tt < hh 90 | int hh = 0, tt = -1; 91 | for(int i = 0; i < n; i++){ 92 | // 判断是否需要弹出队首元素 93 | // 当窗口队列不为空 且 当前下标和队首元素下标的差值大于 k - 1 94 | if(hh <= tt && i - q[hh] > k - 1) hh++; 95 | // 从队尾开始,弹出不大于当前元素的值 96 | while(hh <= tt && a[q[tt]] >= a[i]) tt--; 97 | // 插入当前元素 98 | tt++; 99 | q[tt] = i; 100 | // 队首元素即是当前窗口的最大值,打印出来 101 | // 当窗口元素个数达到 k 个时再输出 102 | if(i >= k - 1) System.out.print(a[q[hh]] + " "); 103 | } 104 | System.out.println(); 105 | // 2. 找滑动窗口的最大值 106 | hh = 0; 107 | tt = -1; 108 | for(int i = 0; i < n; i++){ 109 | if(hh <= tt && i - q[hh] > k - 1) hh++; 110 | while(hh <= tt && a[q[tt]] <= a[i]) tt--; 111 | tt++; 112 | q[tt] = i; 113 | if(i >= k - 1) System.out.print(a[q[hh]] + " "); 114 | } 115 | System.out.println(); 116 | } 117 | } 118 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing240食物链.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 240. 食物链 4 | 5 | `难度:中等` 6 | 7 | ### 题目描述 8 | 9 | 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。 10 | 11 | A吃B, B吃C,C吃A。 12 | 13 | 现有N个动物,以1-N编号。 14 | 15 | 每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 16 | 17 | 有人用两种说法对这N个动物所构成的食物链关系进行描述: 18 | 19 | 第一种说法是”1 X Y”,表示X和Y是同类。 20 | 21 | 第二种说法是”2 X Y”,表示X吃Y。 22 | 23 | 此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。 24 | 25 | 当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 26 | 27 | 1. 当前的话与前面的某些真的话冲突,就是假话; 28 | 2. 当前的话中X或Y比N大,就是假话; 29 | 3. 当前的话表示X吃X,就是假话。 30 | 31 | 你的任务是根据给定的N和K句话,输出假话的总数。 32 | 33 | **输入格式** 34 | 35 | 第一行是两个整数N和K,以一个空格分隔。 36 | 37 | 以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 38 | 39 | 若D=1,则表示X和Y是同类。 40 | 41 | 若D=2,则表示X吃Y。 42 | 43 | **输出格式** 44 | 45 | 只有一个整数,表示假话的数目。 46 | 47 | **数据范围** 48 | 49 | $1≤N≤50000,$ 50 | $0≤K≤100000$ 51 | 52 | ```r 53 | 输入样例: 54 | 55 | 100 7 56 | 1 101 1 57 | 2 1 2 58 | 2 2 3 59 | 2 3 3 60 | 1 1 3 61 | 2 3 1 62 | 1 5 5 63 | 64 | 输出样例: 65 | 66 | 3 67 | ``` 68 | 69 | ### Solution 70 | 71 | 并查集的题目。 72 | 73 | 主要思想是维护每个节点到根节点的距离,详细看注释。 74 | 75 | ```java 76 | // 思想:维护每个节点到共同根节点的距离, 77 | import java.util.*; 78 | import java.io.*; 79 | 80 | class Main{ 81 | static final int N = 50010; 82 | static int[] p = new int[N]; 83 | static int[] dist = new int[N]; 84 | public static int find(int x){ 85 | if(p[x] != x) { 86 | // 重点是怎么更新 dist[x] 87 | // d[x]存储的是x到p[x]的距离,find(x)之后p[x]就是根节点了,所以d[x]存的就是到根节点的距离了 88 | int t = p[x]; 89 | p[x] = find(p[x]); 90 | dist[x] = dist[t] + dist[x]; 91 | } 92 | // return p[x]; 93 | // if(p[x] != x){//不是祖宗节点的话 94 | // int t = find(p[x]); 95 | // dist[x] += dist[p[x]]; 96 | // p[x] = t; 97 | // } 98 | return p[x]; 99 | } 100 | public static void main(String[] args) throws IOException{ 101 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 102 | String[] s = br.readLine().split(" "); 103 | int n = Integer.parseInt(s[0]); 104 | int k = Integer.parseInt(s[1]); 105 | for(int i = 1; i <= n; i++) p[i] = i; 106 | int res = 0; 107 | while(k -- > 0){ 108 | s = br.readLine().split(" "); 109 | int d = Integer.parseInt(s[0]); 110 | int x = Integer.parseInt(s[1]); 111 | int y = Integer.parseInt(s[2]); 112 | if(x > n || y > n) res++; 113 | else{ 114 | // 先找 x,y 的根节点 115 | int px = find(x), py = find(y); 116 | // 判断是不是同类关系 117 | if(d == 1){ 118 | // 如果 x 和 y 在一个集合中,判断两者距离根节点的距离是否刚好被 3 整除 119 | if(px == py && (dist[x] - dist[y]) % 3 != 0) res ++; 120 | // 如果 x 和 y 不在一个集合中,就把两者连起来 121 | // 连起来要保证 dist[x] - dist[y] 能够被 3 整除 122 | else if(px != py){ 123 | p[px] = py; 124 | dist[px] = dist[y] - dist[x]; 125 | } 126 | } 127 | // 判断是不是吃与被吃的关系 128 | else if(d == 2){ 129 | // 如果在一个集合中,就要判断是否关系属实,就是差 1 130 | // 如果不在一个集合中,就建立关系 131 | // x 吃 y 132 | // 我们定义 133 | // y - x 模 3 为 1 表示 y 吃 x 134 | // y - x 模 3 为 2 表示 x 吃 y 135 | // 防止出现取模出现负数不好判断,就先减去相应的值,看最后的模是否为 0 136 | if(x == y) res ++; 137 | else if(px == py && (dist[x] - dist[y] - 1) % 3 != 0) res++; 138 | else if(px != py){ 139 | p[px] = p[y]; 140 | dist[px] = dist[y] + 1 - dist[x]; 141 | } 142 | } 143 | } 144 | } 145 | System.out.println(res); 146 | } 147 | } 148 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing3302表达式求值.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 3302表达式求值 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。 10 | 11 | 注意: 12 | 13 | - 数据保证给定的表达式合法。 14 | - 题目保证符号 - 只作为减号出现,不会作为负号出现,例如,`-1+2,(2+2)*(-(1+1)+2)` 之类表达式均不会出现。 15 | - 题目保证表达式中所有数字均为正整数。 16 | - 题目保证表达式在中间计算过程以及结果中,均不超过 2^31−1。 17 | - 题目中的整除是指向 0 取整,也就是说对于大于 0 的结果向下取整,例如 5/3=1,对于小于 0 的结果向上取整,例如 5/(1−4)=−1。 18 | - C++和Java中的整除默认是向零取整;Python中的整除//默认向下取整,因此Python的eval()函数中的整除也是向下取整,在本题中不能直接使用。 19 | 20 | **输入格式** 21 | 22 | 共一行,为给定表达式。 23 | 24 | **输出格式** 25 | 26 | 共一行,为表达式的结果。 27 | 28 | **数据范围** 29 | 30 | 表达式的长度不超过 10^5。 31 | 32 | ```r 33 | 输入样例: 34 | 35 | (2+2)*(1+1) 36 | 37 | 输出样例: 38 | 39 | 8 40 | ``` 41 | 42 | ### Solution 43 | 44 | ```java 45 | import java.util.*; 46 | 47 | class Main{ 48 | public static void main(String[] args) { 49 | Scanner sc = new Scanner(System.in); 50 | String s = sc.nextLine(); 51 | int n = s.length(); 52 | Deque num = new LinkedList<>(); 53 | Deque op = new LinkedList<>(); 54 | Map pr = new HashMap<>(); 55 | pr.put('+', 1); 56 | pr.put('-', 1); 57 | pr.put('*', 2); 58 | pr.put('/', 2); 59 | for(int i = 0; i < n; i++) { 60 | char c = s.charAt(i); 61 | if(c == ' ') continue; 62 | else if(Character.isDigit(c)) { 63 | int j = i, x = 0; 64 | while(j < n && Character.isDigit(s.charAt(j))) { 65 | x = x * 10 + (s.charAt(j) - '0'); 66 | j++; 67 | } 68 | num.push(x); 69 | i = j - 1; 70 | } 71 | else if(c == '(') op.push(c); 72 | else if(c == ')') { 73 | while(op.peek() != '(') cal(num, op); 74 | op.pop(); 75 | } 76 | else { 77 | while(!op.isEmpty() && op.peek() != '(' && pr.get(op.peek()) >= pr.get(c)) cal(num, op); 78 | op.push(c); 79 | } 80 | } 81 | while(!op.isEmpty()) cal(num, op); 82 | System.out.println(num.pop()); 83 | } 84 | public static void cal(Deque num, Deque op) { 85 | int b = num.pop(); 86 | int a = num.pop(); 87 | char c = op.pop(); 88 | int x = 0; 89 | if (c == '+') x = a + b; 90 | else if (c == '-') x = a - b; 91 | else if (c == '*') x = a * b; 92 | else x = a / b; 93 | num.push(x); 94 | } 95 | } 96 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing826单链表.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 826. 单链表 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 实现一个单链表,链表初始为空,支持三种操作: 10 | 11 | (1) 向链表头插入一个数; 12 | 13 | (2) 删除第k个插入的数后面的数; 14 | 15 | (3) 在第k个插入的数后插入一个数 16 | 17 | 现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。 18 | 19 | 注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,…第n个插入的数。 20 | 21 | **输入格式** 22 | 23 | 第一行包含整数M,表示操作次数。 24 | 25 | 接下来M行,每行包含一个操作命令,操作命令可能为以下几种: 26 | 27 | (1) “H x”,表示向链表头插入一个数x。 28 | 29 | (2) “D k”,表示删除第k个输入的数后面的数(当k为0时,表示删除头结点)。 30 | 31 | (3) “I k x”,表示在第k个输入的数后面插入一个数x(此操作中k均大于0)。 32 | 33 | **输出格式** 34 | 35 | 共一行,将整个链表从头到尾输出。 36 | 37 | ```r 38 | 数据范围 39 | 40 | 1≤M≤100000 41 | 42 | 43 | 所有操作保证合法。 44 | 输入样例: 45 | 46 | 10 47 | H 9 48 | I 1 1 49 | D 1 50 | D 0 51 | H 6 52 | I 3 6 53 | I 4 5 54 | I 4 5 55 | I 3 4 56 | D 6 57 | 58 | 输出样例: 59 | 60 | 6 4 6 5 61 | ``` 62 | 63 | ### Solution 64 | 65 | ```java 66 | import java.util.*; 67 | import java.io.*; 68 | 69 | public class Main{ 70 | public static int N = 100010; 71 | public static int[] e = new int[N]; 72 | public static int[] ne = new int[N]; 73 | public static int head = 0; 74 | public static int idx = 1; 75 | public static void main(String[] args) throws IOException{ 76 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 77 | int m = Integer.parseInt(in.readLine()); 78 | while(m > 0){ 79 | m--; 80 | String[] s = in.readLine().split(" "); 81 | if(s[0].equals("H")){ 82 | int x = Integer.parseInt(s[1]); 83 | addHead(x); 84 | }else if(s[0].equals("D")){ 85 | int k = Integer.parseInt(s[1]); 86 | remove(k); 87 | }else{ 88 | int k = Integer.parseInt(s[1]); 89 | int x = Integer.parseInt(s[2]); 90 | add(k, x); 91 | } 92 | } 93 | for(int i = head; i != 0; i = ne[i]) System.out.print(e[i] + " "); 94 | 95 | } 96 | public static void addHead(int x){ 97 | // 先记录 idx 的值 98 | // 然后让 idx 指向头结点指向的结点 99 | // 修改头结点的指向为 idx 100 | // idx 加 1 101 | e[idx] = x; 102 | ne[idx] = head; 103 | head = idx; 104 | idx++; 105 | } 106 | public static void remove(int k){ 107 | // 如果 k 为 0,删除头结点 108 | if(k == 0) head = ne[head]; 109 | // 第 k 个结点指向 next 的 next 110 | else ne[k] = ne[ne[k]]; 111 | } 112 | public static void add(int k, int x){ 113 | // 先记录 idx 的值 114 | // 然后让 idx 指向第 k 指向的结点 115 | // 修改第 k 个结点的指向为 idx 116 | // idx 加 1 117 | e[idx] = x; 118 | ne[idx] = ne[k]; 119 | ne[k] = idx; 120 | idx++; 121 | } 122 | } 123 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing827双链表.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 827. 双链表 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 实现一个双链表,双链表初始为空,支持5种操作: 10 | 11 | (1) 在最左侧插入一个数; 12 | 13 | (2) 在最右侧插入一个数; 14 | 15 | (3) 将第k个插入的数删除; 16 | 17 | (4) 在第k个插入的数左侧插入一个数; 18 | 19 | (5) 在第k个插入的数右侧插入一个数 20 | 21 | 现在要对该链表进行M次操作,进行完所有操作后,从左到右输出整个链表。 22 | 23 | **注意**:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,…第n个插入的数。 24 | 25 | **输入格式** 26 | 27 | 第一行包含整数M,表示操作次数。 28 | 29 | 接下来M行,每行包含一个操作命令,操作命令可能为以下几种: 30 | 31 | (1) “L x”,表示在链表的最左端插入数x。 32 | 33 | (2) “R x”,表示在链表的最右端插入数x。 34 | 35 | (3) “D k”,表示将第k个插入的数删除。 36 | 37 | (4) “IL k x”,表示在第k个插入的数左侧插入一个数。 38 | 39 | (5) “IR k x”,表示在第k个插入的数右侧插入一个数。 40 | 41 | **输出格式** 42 | 43 | 共一行,将整个链表从左到右输出。 44 | 45 | ```r 46 | 数据范围 47 | 48 | 1≤M≤100000 49 | 50 | 51 | 所有操作保证合法。 52 | 输入样例: 53 | 54 | 10 55 | R 7 56 | D 1 57 | L 3 58 | IL 2 10 59 | D 3 60 | IL 2 7 61 | L 8 62 | R 9 63 | IL 4 7 64 | IR 2 2 65 | 66 | 输出样例: 67 | 68 | 8 7 7 3 2 9 69 | ``` 70 | 71 | ### Solution 72 | 73 | ```java 74 | import java.util.*; 75 | import java.io.*; 76 | 77 | class Main{ 78 | static final int N = 100010; 79 | static int[] e = new int[N]; 80 | static int[] l = new int[N]; 81 | static int[] r = new int[N]; 82 | static int idx = 1; 83 | static int R = 100005; 84 | static int L = 0; 85 | public static void main(String[] args) throws IOException{ 86 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 87 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 88 | String[] s = br.readLine().split(" "); 89 | int m = Integer.parseInt(s[0]); 90 | // 先把两个哨兵节点连上 91 | l[R] = L; 92 | r[L] = R; 93 | while(m > 0){ 94 | m--; 95 | s = br.readLine().split(" "); 96 | if(s[0].equals("L")){ 97 | int x = Integer.parseInt(s[1]); 98 | // 链表的最左端等价在 0 的右端 99 | add(0, x); 100 | } 101 | else if(s[0].equals("R")){ 102 | int x = Integer.parseInt(s[1]); 103 | // 链表的最右端等价 l[R] 的右端 104 | add(l[R], x); 105 | } 106 | else if(s[0].equals("D")){ 107 | int k = Integer.parseInt(s[1]); 108 | delete(k); 109 | } 110 | else if(s[0].equals("IL")){ 111 | int k = Integer.parseInt(s[1]); 112 | int x = Integer.parseInt(s[2]); 113 | // k 的左侧等价于 l[k] 的右侧 114 | add(l[k], x); 115 | } 116 | else if(s[0].equals("IR")){ 117 | int k = Integer.parseInt(s[1]); 118 | int x = Integer.parseInt(s[2]); 119 | // 就是在 k 的右侧 120 | add(k, x); 121 | } 122 | } 123 | // System.out.println(e[1]); 124 | for(int i = r[L]; i != R; i = r[i]) bw.write(e[i] + " "); 125 | bw.close(); 126 | } 127 | public static void add(int k, int x){ 128 | e[idx] = x; 129 | r[idx] = r[k]; 130 | l[idx] = l[r[k]]; 131 | l[r[k]] = idx; 132 | r[k] = idx; 133 | idx++; 134 | } 135 | public static void delete(int k) { 136 | r[l[k]] = r[k]; 137 | l[r[k]] = l[k]; 138 | } 139 | 140 | } 141 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing828模拟栈.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 828. 模拟栈 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 实现一个栈,栈初始为空,支持四种操作: 10 | 11 | (1) “push x” – 向栈顶插入一个数x; 12 | 13 | (2) “pop” – 从栈顶弹出一个数; 14 | 15 | (3) “empty” – 判断栈是否为空; 16 | 17 | (4) “query” – 查询栈顶元素。 18 | 19 | 现在要对栈进行M个操作,其中的每个操作3和操作4都要输出相应的结果。 20 | 21 | **输入格式** 22 | 23 | 第一行包含整数M,表示操作次数。 24 | 25 | 接下来M行,每行包含一个操作命令,操作命令为”push x”,”pop”,”empty”,”query”中的一种。 26 | 27 | **输出格式** 28 | 29 | 对于每个”empty”和”query”操作都要输出一个查询结果,每个结果占一行。 30 | 31 | 其中,”empty”操作的查询结果为“YES”或“NO”,”query”操作的查询结果为一个整数,表示栈顶元素的值。 32 | 33 | ```r 34 | 数据范围 35 | 36 | 1≤M≤100000, 37 | 1≤x≤109 38 | 39 | 40 | 所有操作保证合法。 41 | 输入样例: 42 | 43 | 10 44 | push 5 45 | query 46 | push 6 47 | pop 48 | query 49 | pop 50 | empty 51 | push 4 52 | query 53 | empty 54 | 55 | 输出样例: 56 | 57 | 5 58 | 5 59 | YES 60 | 4 61 | NO 62 | ``` 63 | 64 | ### Solution 65 | 66 | ```java 67 | import java.util.*; 68 | import java.io.*; 69 | 70 | class Main{ 71 | static final int N = 100010; 72 | static int[] q = new int[N]; 73 | static int hh = 0; 74 | public static void main(String[] args) throws IOException{ 75 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 76 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 77 | String[] s = br.readLine().split(" "); 78 | int m = Integer.parseInt(s[0]); 79 | while(m > 0){ 80 | m--; 81 | s = br.readLine().split(" "); 82 | if(s[0].equals("push")){ 83 | int x = Integer.parseInt(s[1]); 84 | q[hh++] = x; 85 | 86 | } 87 | else if(s[0].equals("pop")){ 88 | hh--; 89 | } 90 | else if(s[0].equals("empty")){ 91 | if(hh == 0) bw.write("YES" + "\n"); 92 | else bw.write("NO" + "\n"); 93 | } 94 | else if(s[0].equals("query")){ 95 | bw.write(q[hh - 1] + "\n"); 96 | } 97 | } 98 | bw.close(); 99 | br.close(); 100 | } 101 | } 102 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing829模拟队列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 829. 模拟队列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 实现一个队列,队列初始为空,支持四种操作: 10 | 11 | (1) “push x” – 向队尾插入一个数x; 12 | 13 | (2) “pop” – 从队头弹出一个数; 14 | 15 | (3) “empty” – 判断队列是否为空; 16 | 17 | (4) “query” – 查询队头元素。 18 | 19 | 现在要对队列进行M个操作,其中的每个操作3和操作4都要输出相应的结果。 20 | 21 | **输入格式** 22 | 23 | 第一行包含整数M,表示操作次数。 24 | 25 | 接下来M行,每行包含一个操作命令,操作命令为”push x”,”pop”,”empty”,”query”中的一种。 26 | 27 | **输出格式** 28 | 29 | 对于每个”empty”和”query”操作都要输出一个查询结果,每个结果占一行。 30 | 31 | 其中,”empty”操作的查询结果为“YES”或“NO”,”query”操作的查询结果为一个整数,表示队头元素的值。 32 | 33 | ```r 34 | 数据范围 35 | 36 | 1≤M≤100000, 37 | 1≤x≤109, 38 | 39 | 所有操作保证合法。 40 | 输入样例: 41 | 42 | 10 43 | push 6 44 | empty 45 | query 46 | pop 47 | empty 48 | push 3 49 | push 4 50 | pop 51 | query 52 | push 6 53 | 54 | 输出样例: 55 | 56 | NO 57 | 6 58 | YES 59 | 4 60 | ``` 61 | 62 | ### Solution 63 | 64 | ```java 65 | import java.util.*; 66 | import java.io.*; 67 | 68 | class Main{ 69 | static final int N = 100010; 70 | static int[] queue = new int[N]; 71 | static int hh = 0, tt = 0; 72 | public static void main(String[] args) throws IOException{ 73 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 74 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 75 | String[] s = br.readLine().split(" "); 76 | int m = Integer.parseInt(s[0]); 77 | while(m > 0){ 78 | m--; 79 | s = br.readLine().split(" "); 80 | if(s[0].equals("push")){ 81 | int x = Integer.parseInt(s[1]); 82 | queue[tt] = x; 83 | tt++; 84 | } 85 | else if(s[0].equals("pop")){ 86 | hh++; 87 | } 88 | else if(s[0].equals("empty")){ 89 | if(hh >= tt) bw.write("YES\n"); 90 | else bw.write("NO\n"); 91 | } 92 | else if(s[0].equals("query")){ 93 | bw.write(queue[hh] + "\n"); 94 | } 95 | } 96 | bw.close(); 97 | br.close(); 98 | } 99 | } 100 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing830单调栈.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 830. 单调栈 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数N,表示数列长度。 14 | 15 | 第二行包含N个整数,表示整数数列。 16 | 17 | **输出格式** 18 | 19 | 共一行,包含N个整数,其中第i个数表示第i个数的左边第一个比它小的数,如果不存在则输出-1。 20 | 21 | ```r 22 | 数据范围 23 | 24 | 1≤N≤105 25 | 26 | 1≤数列中元素≤109 27 | 28 | 输入样例: 29 | 30 | 5 31 | 3 4 2 7 5 32 | 33 | 输出样例: 34 | 35 | -1 3 -1 2 2 36 | ``` 37 | 38 | ### Solution 39 | 40 | ```java 41 | import java.util.*; 42 | import java.io.*; 43 | 44 | class Main{ 45 | static final int N = 100010; 46 | static int[] a = new int[N]; 47 | static int[] stk = new int[N]; 48 | static int hh = 0; 49 | public static void main(String[] args) throws IOException{ 50 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 51 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 52 | String[] s = br.readLine().split(" "); 53 | int n = Integer.parseInt(s[0]); 54 | s = br.readLine().split(" "); 55 | for(int i = 0; i < n; i ++) a[i] = Integer.parseInt(s[i]); 56 | // 输出每个数左边第一个比它小的数 57 | // 单调递增的栈 58 | for(int i = 0; i < n; i++){ 59 | while(hh > 0 && a[stk[hh]] >= a[i]) hh--; 60 | if(hh == 0) bw.write("-1 "); 61 | else bw.write(a[stk[hh]] + " "); 62 | hh++; 63 | stk[hh] = i; 64 | } 65 | bw.close(); 66 | br.close(); 67 | } 68 | } 69 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing831KMP字符串.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 831. KMP字符串 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。 10 | 11 | 模板串P在模式串S中多次作为子串出现。 12 | 13 | 求出模板串P在模式串S中所有出现的位置的起始下标。 14 | 15 | **输入格式** 16 | 17 | 第一行输入整数N,表示字符串P的长度。 18 | 19 | 第二行输入字符串P。 20 | 21 | 第三行输入整数M,表示字符串S的长度。 22 | 23 | 第四行输入字符串S。 24 | 25 | **输出格式** 26 | 27 | 共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。 28 | 29 | **数据范围** 30 | 31 | $1≤N≤10^5$ 32 | 33 | $1≤M≤10^6$ 34 | 35 | 36 | 37 | **输入样例:** 38 | 39 | ```R 40 | 3 41 | aba 42 | 5 43 | ababa 44 | ``` 45 | 46 | **输出样例:** 47 | 48 | ```R 49 | 0 2 50 | ``` 51 | 52 | ### Solution 53 | 54 | 1. 暴力算法怎么做 55 | 56 | ```javascript 57 | S[N], p[M]; 58 | for(int i = 1; i <= n; i++) { 59 | boolean flag = true; 60 | for(int j = 1; j <= m; j++) { 61 | if(s[i + j - 1] != p[j]) { 62 | flag = false; 63 | break; 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | 2. KMP 优化 70 | 71 | ```java 72 | import java.util.*; 73 | import java.io.*; 74 | 75 | class Main{ 76 | public static void main(String[] args) throws IOException{ 77 | int N = 100010; 78 | int M = 1000010; 79 | int[] ne = new int[N]; 80 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 81 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 82 | Integer n = Integer.parseInt(br.readLine()); 83 | String sp = " " + br.readLine(); 84 | Integer m = Integer.parseInt(br.readLine()); 85 | String ss = " " + br.readLine(); 86 | char[] p = sp.toCharArray(); 87 | char[] s = ss.toCharArray(); 88 | 89 | for(int i = 2, j = 0; i <= n; i++) { 90 | while(j != 0 && p[i] != p[j + 1]) j = ne[j]; 91 | if(p[i] == p[j + 1]) j++; 92 | ne[i] = j; 93 | } 94 | for(int i = 1, j = 0; i <= m; i++) { 95 | while(j != 0 && s[i] != p[j + 1]) j = ne[j]; 96 | if(s[i] == p[j + 1]) j++; 97 | if(j == n) { 98 | bw.write(i - n + " "); 99 | j = ne[j]; 100 | } 101 | } 102 | bw.close(); 103 | br.close(); 104 | } 105 | } 106 | ``` 107 | 108 | ### yxc 109 | 110 | 1. KMP 匹配过程 111 | 112 | ![image-20210217195208053](pics/image-20210217195208053.png) 113 | 114 | 2. 求 next 数组 115 | 116 | ![image-20210217195854304](pics/image-20210217195854304.png) 117 | 118 | ```cpp 119 | #include 120 | 121 | using namespace std; 122 | 123 | const int N = 100010, M = 1000010; 124 | 125 | int n, m; 126 | int ne[N]; 127 | char s[M], p[N]; 128 | 129 | int main() 130 | { 131 | cin >> n >> p + 1 >> m >> s + 1; 132 | 133 | // 求 next 数组过程 134 | for (int i = 2, j = 0; i <= n; i ++ ) 135 | { 136 | while (j && p[i] != p[j + 1]) j = ne[j]; 137 | if (p[i] == p[j + 1]) j ++ ; 138 | ne[i] = j; 139 | } 140 | 141 | // kmp 匹配过程 142 | for (int i = 1, j = 0; i <= m; i ++ ) 143 | { 144 | while (j && s[i] != p[j + 1]) j = ne[j]; 145 | if (s[i] == p[j + 1]) j ++ ; 146 | if (j == n) 147 | { 148 | printf("%d ", i - n); 149 | j = ne[j]; 150 | } 151 | } 152 | 153 | return 0; 154 | } 155 | 156 | // 作者:yxc 157 | // 链接:https://www.acwing.com/activity/content/code/content/43108/ 158 | // 来源:AcWing 159 | // 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 160 | ``` 161 | 162 | -------------------------------------------------------------------------------- /2数据结构/AcWing835Trie字符串统计.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 835. Trie字符串统计 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 维护一个字符串集合,支持两种操作: 10 | 11 | 1. “I x”向集合中插入一个字符串x; 12 | 2. “Q x”询问一个字符串在集合中出现了多少次。 13 | 14 | 共有N个操作,输入的字符串总长度不超过 $10^5$,字符串仅包含小写英文字母。 15 | 16 | **输入格式** 17 | 18 | 第一行包含整数N,表示操作数。 19 | 20 | 接下来N行,每行包含一个操作指令,指令为”I x”或”Q x”中的一种。 21 | 22 | **输出格式** 23 | 24 | 对于每个询问指令”Q x”,都要输出一个整数作为结果,表示x在集合中出现的次数。 25 | 26 | 每个结果占一行。 27 | 28 | **数据范围** 29 | 30 | $1 ≤ N ≤ 2 ∗ 10^4$ 31 | 32 | ```r 33 | 输入样例: 34 | 35 | 5 36 | I abc 37 | Q abc 38 | Q ab 39 | I ab 40 | Q ab 41 | 42 | 输出样例: 43 | 44 | 1 45 | 0 46 | 1 47 | 48 | ``` 49 | 50 | ### Solution 51 | 52 | 1. 偷懒做法,用一个哈希表存储,做到时间复杂度为 O(n)。 53 | 54 | ```java 55 | import java.util.*; 56 | import java.io.*; 57 | 58 | public class Main{ 59 | public static void main(String[] args) throws IOException{ 60 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 61 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 62 | int n = Integer.parseInt(br.readLine()); 63 | Map map = new HashMap<>(); 64 | String[] s; 65 | while(n > 0){ 66 | n--; 67 | s = br.readLine().split(" "); 68 | if(s[0].equals("I")){ 69 | map.put(s[1], map.getOrDefault(s[1], 0) + 1); 70 | }else{ 71 | bw.write(map.getOrDefault(s[1], 0) + "\n"); 72 | } 73 | } 74 | bw.close(); 75 | br.close(); 76 | } 77 | } 78 | ``` 79 | 80 | 2. 构造 Trie 树,Trie 树是**高效存储和查找字符串集合的数据结构**。 81 | 82 | ```java 83 | import java.util.*; 84 | import java.io.*; 85 | 86 | public class Main{ 87 | public static int N = 100010; 88 | // 记录当前是第几个结点 89 | public static int idx = 0; 90 | // 记录以当前结点为结尾的个数 91 | public static int[] cnt = new int[N]; 92 | // 二维矩阵, N 为总字符串长度, 26 为每个结点最大可能有 26 个分支 93 | public static int[][] trie = new int[N][26]; 94 | public static void main(String[] args) throws IOException{ 95 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 96 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 97 | int n = Integer.parseInt(br.readLine()); 98 | String[] s; 99 | while(n-- > 0){ 100 | s = br.readLine().split(" "); 101 | if(s[0].equals("I")){ 102 | insert(s[1]); 103 | }else{ 104 | int res = query(s[1]); 105 | bw.write(res + "\n"); 106 | } 107 | } 108 | bw.close(); 109 | br.close(); 110 | } 111 | public static void insert(String s){ 112 | // 每个字符串都从字典树的 0 级也就是 跟开始找 113 | int p = 0; 114 | for(int i =0; i < s.length(); i++){ 115 | int t = s.charAt(i) - 'a'; 116 | // 如果这个 字母不存在,就新建一个结点,并且指向它 117 | if(trie[p][t] == 0) { 118 | // idx ++ 119 | idx ++; 120 | trie[p][t] = idx; 121 | } 122 | // 更新结点序号 123 | p = trie[p][t]; 124 | } 125 | // 更新以 idx 为结尾的字符串的个数 126 | cnt[p] ++; 127 | } 128 | public static int query(String s){ 129 | int p = 0; 130 | for(int i = 0; i < s.length(); i++){ 131 | int t = s.charAt(i) - 'a'; 132 | if(trie[p][t] == 0) return 0; 133 | p = trie[p][t]; 134 | } 135 | return cnt[p]; 136 | } 137 | } 138 | ``` 139 | 140 | ### yxc 141 | 142 | ![image-20210217161943396](pics/image-20210217161943396.png) -------------------------------------------------------------------------------- /2数据结构/AcWing836合并集合.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 836. 合并集合 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 一共有n个数,编号是1~n,最开始每个数各自在一个集合中。 10 | 11 | 现在要进行m个操作,操作共有两种: 12 | 13 | - “M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作; 14 | - “Q a b”,询问编号为a和b的两个数是否在同一个集合中; 15 | 16 | **输入格式** 17 | 18 | 第一行输入整数n和m。 19 | 20 | 接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。 21 | 22 | **输出格式** 23 | 24 | 对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。 25 | 26 | 每个结果占一行。 27 | 28 | **数据范围** 29 | 30 | $1≤n,m≤10^5$ 31 | 32 | ```r 33 | 输入样例: 34 | 35 | 4 5 36 | M 1 2 37 | M 3 4 38 | Q 1 2 39 | Q 1 3 40 | Q 3 4 41 | 42 | 输出样例: 43 | 44 | Yes 45 | No 46 | Yes 47 | ``` 48 | 49 | ### Solution 50 | 51 | 并查集结构:1. 快速合并两个集合,快速查询两个元素是否在同一个集合中 52 | 53 | 注意 `find(int x)` 函数中的路径压缩 `p[x] = find[x]` 54 | 55 | ```java 56 | import java.util.*; 57 | import java.io.*; 58 | 59 | public class Main{ 60 | public static int N = 100010; 61 | // 存储每个节点的父节点 62 | public static int[] p = new int[N]; 63 | public static void main(String[] args) throws IOException{ 64 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 65 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 66 | String[] s = br.readLine().split(" "); 67 | int n = Integer.parseInt(s[0]); 68 | int m = Integer.parseInt(s[1]); 69 | // 先初始化每个节点为自己的父节点 70 | for(int i = 1; i <= n; i++){ 71 | p[i] = i; 72 | } 73 | while(m-- > 0){ 74 | s = br.readLine().split(" "); 75 | int a = Integer.parseInt(s[1]); 76 | int b = Integer.parseInt(s[2]); 77 | if(s[0].equals("M")){ 78 | p[find(a)] = find(b); 79 | }else{ 80 | if(find(a) == find(b)){ 81 | bw.write("Yes" + "\n"); 82 | }else{ 83 | bw.write("No" + "\n"); 84 | } 85 | } 86 | } 87 | bw.close(); 88 | br.close(); 89 | } 90 | // 找到 x 的最后的父节点也就是根节点 91 | // 路径压缩:每个节点都指向根节点 92 | public static int find(int x){ 93 | if(p[x] != x) p[x] = find(p[x]); 94 | return p[x]; 95 | } 96 | } 97 | 98 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing837连通块中点的数量.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 837. 连通块中点的数量 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。 10 | 11 | 现在要进行m个操作,操作共有三种: 12 | 13 | - “C a b”,在点a和点b之间连一条边,a和b可能相等; 14 | - “Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等; 15 | - “Q2 a”,询问点a所在连通块中点的数量; 16 | 17 | **输入格式** 18 | 19 | 第一行输入整数n和m。 20 | 21 | 接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。 22 | 23 | **输出格式** 24 | 25 | 对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。 26 | 27 | 对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量 28 | 29 | 每个结果占一行。 30 | 31 | **数据范围** 32 | 33 | $1≤n,m≤10^5$ 34 | 35 | ```r 36 | 输入样例: 37 | 38 | 5 5 39 | C 1 2 40 | Q1 1 2 41 | Q2 1 42 | C 2 5 43 | Q2 5 44 | 45 | 输出样例: 46 | 47 | Yes 48 | 2 49 | 3 50 | ``` 51 | 52 | ### Solution 53 | 54 | 这道题在并查集的基础上,还有一个计算每个连通块的点的数量。 55 | 56 | 每个连通块的点的数量也就是这个连通块根节点所在连通块中点的数量。 57 | ```java 58 | import java.util.*; 59 | import java.io.*; 60 | 61 | public class Main{ 62 | public static int N = 100010; 63 | public static int[] cnt = new int[N]; 64 | public static int[] p = new int[N]; 65 | public static void main(String[] args) throws IOException{ 66 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 67 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 68 | String[] s = br.readLine().split(" "); 69 | int n = Integer.parseInt(s[0]); 70 | // 初始化,每个节点的父节点是自己 71 | for(int i = 1; i <= n; i++){ 72 | p[i] = i; 73 | cnt[i] = 1; 74 | } 75 | int m = Integer.parseInt(s[1]); 76 | while(m-- > 0){ 77 | s = br.readLine().split(" "); 78 | // 合并连通块 79 | if(s[0].equals("C")){ 80 | int a = Integer.parseInt(s[1]); 81 | int b = Integer.parseInt(s[2]); 82 | a = find(a); 83 | b = find(b); 84 | if(a != b){ 85 | p[a] = b; 86 | cnt[b] = cnt[a] + cnt[b]; 87 | } 88 | } 89 | // 两个点是否在一个连通块 90 | else if(s[0].equals("Q1")){ 91 | int a = Integer.parseInt(s[1]); 92 | int b = Integer.parseInt(s[2]); 93 | if(find(a) == find(b)) bw.write("Yes\n"); 94 | else bw.write("No\n"); 95 | } 96 | // 点 a 所在连通块的点的数量 97 | else if(s[0].equals("Q2")){ 98 | int a = Integer.parseInt(s[1]); 99 | bw.write(cnt[find(a)] + "\n"); 100 | } 101 | bw.flush(); 102 | } 103 | bw.close(); 104 | br.close(); 105 | } 106 | public static int find(int x){ 107 | if(p[x] != x) p[x] = find(p[x]); 108 | return p[x]; 109 | } 110 | } 111 | ``` -------------------------------------------------------------------------------- /2数据结构/AcWing838堆排序.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 838. 堆排序 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 输入一个长度为n的整数数列,从小到大输出前m小的数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n和m。 14 | 15 | 第二行包含n个整数,表示整数数列。 16 | 17 | **输出格式** 18 | 19 | 共一行,包含m个整数,表示整数数列中前m小的数。 20 | 21 | **数据范围** 22 | 23 | $1≤m≤n≤10^5$, 24 | $1≤数列中元素≤10^9$ 25 | 26 | ```r 27 | 5 3 28 | 4 5 1 3 2 29 | 30 | 输出样例: 31 | 32 | 1 2 3 33 | ``` 34 | 35 | ### Solution 36 | 37 | ```java 38 | import java.util.*; 39 | import java.io.*; 40 | 41 | public class Main{ 42 | public static final int N = 100010; 43 | public static int[] h = new int[N]; 44 | public static int cnt = 0; 45 | public static void main(String[] args) throws IOException{ 46 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 47 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 48 | String[] s = br.readLine().split(" "); 49 | int n = Integer.parseInt(s[0]); 50 | int m = Integer.parseInt(s[1]); 51 | s = br.readLine().split(" "); 52 | // 初始化 53 | for(int i = 1; i <= n; i++) h[i] = Integer.parseInt(s[i - 1]); 54 | cnt = n; 55 | // 建堆,从 n / 2 开始建堆,可以把复杂度降低到 O(n) 56 | for(int i = n / 2; i > 0; i--) down(i); 57 | while(m-- > 0){ 58 | bw.write(h[1] + " "); 59 | h[1] = h[cnt]; 60 | cnt--; 61 | down(1); 62 | } 63 | bw.close(); 64 | br.close(); 65 | } 66 | public static void down(int u){ 67 | int t = u; 68 | // 找到 u 点和他左右孩子的三个的最小值 69 | // 如果左右孩子有比 u 小的,就互换,然后递归下去 70 | if(2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u; 71 | if(2 * u + 1 <= cnt && h[2 * u + 1] < h[t]) t = 2 * u + 1; 72 | if(u != t){ 73 | swap(u, t); 74 | down(t); 75 | } 76 | } 77 | public static void swap(int u, int t){ 78 | int temp = h[u]; 79 | h[u] = h[t]; 80 | h[t] = temp; 81 | } 82 | } 83 | ``` 84 | 85 | ### yxc 86 | 87 | 下标从 1 开始比较好,从 0 开始的画, `2 * 0` 还是 0 88 | 89 | ![image-20210217161426667](pics/image-20210217161426667.png) 90 | 91 | 从 `1 ~ n/2`开始建堆,时间复杂度为 `O(n)`,证明如下 92 | 93 | ![image-20210218150532815](pics/image-20210218150532815.png) 94 | 95 | 96 | 97 | 98 | 99 | ```cpp 100 | // h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1 101 | // ph[k]存储第k个插入的点在堆中的位置 102 | // hp[k]存储堆中下标是k的点是第几个插入的 103 | int h[N], ph[N], hp[N], size; 104 | 105 | // 交换两个点,及其映射关系 106 | void heap_swap(int a, int b) 107 | { 108 | swap(ph[hp[a]],ph[hp[b]]); 109 | swap(hp[a], hp[b]); 110 | swap(h[a], h[b]); 111 | } 112 | 113 | void down(int u) 114 | { 115 | int t = u; 116 | if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2; 117 | if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1; 118 | if (u != t) 119 | { 120 | heap_swap(u, t); 121 | down(t); 122 | } 123 | } 124 | 125 | void up(int u) 126 | { 127 | while (u / 2 && h[u] < h[u / 2]) 128 | { 129 | heap_swap(u, u / 2); 130 | u >>= 1; 131 | } 132 | } 133 | 134 | // O(n)建堆 135 | for (int i = n / 2; i; i -- ) down(i); 136 | 137 | 作者:yxc 138 | 链接:https://www.acwing.com/blog/content/404/ 139 | 来源:AcWing 140 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 141 | ``` 142 | 143 | -------------------------------------------------------------------------------- /2数据结构/AcWing840模拟散列表.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 840. 模拟散列表 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 维护一个集合,支持如下几种操作: 10 | 11 | - “I x”,插入一个数x; 12 | - “Q x”,询问数x是否在集合中出现过; 13 | 14 | 现在要进行N次操作,对于每个询问操作输出对应的结果。 15 | 16 | **输入格式** 17 | 18 | 第一行包含整数N,表示操作数量。 19 | 20 | 接下来N行,每行包含一个操作指令,操作指令为”I x”,”Q x”中的一种。 21 | 22 | **输出格式** 23 | 24 | 对于每个询问指令“Q x”,输出一个询问结果,如果x在集合中出现过,则输出“Yes”,否则输出“No”。 25 | 26 | 每个结果占一行。 27 | 28 | **数据范围** 29 | 30 | $1≤N≤10^5$ 31 | 32 | $−10^9≤x≤10^9$ 33 | 34 | ```r 35 | 输入样例: 36 | 37 | 5 38 | I 1 39 | I 2 40 | I 3 41 | Q 2 42 | Q 5 43 | 44 | 输出样例: 45 | 46 | Yes 47 | No 48 | ``` 49 | 50 | ### Solution 51 | 52 | 模拟散列表要注意两点:1. 哈希算法:x mod $10^5$(取模的值尽量取成质数,开放寻址法要取 2~3 倍的空间);2. 解决冲突,开放寻址法或者拉链法。 53 | 54 | 1. 拉链法 55 | 56 | ```java 57 | import java.util.*; 58 | import java.io.*; 59 | 60 | class Main{ 61 | // 拉链法,哈希到相同位置拉一条单链表出来 62 | // 单链表就是用数组来模拟单链表,之前的例题已经做过了 63 | // N 要取最接近 100000 的质数 64 | public static int N = 100003; 65 | // h 数组下标是 x 哈希之后的值,存储的值相应 idx 66 | public static int[] h = new int[N]; 67 | // e[idx] 表示 h 引出单链表下标为 idx 的值 68 | public static int[] e = new int[N]; 69 | // ne[idx] 存储下标为 idx 的下一个位置的下标 70 | public static int[] ne = new int[N]; 71 | // idx 单链表的下标,从 1 开始,因为 h 数组初始化为 0 代表空,如果 idx 从 0 开始,会矛盾 72 | public static int idx = 1; 73 | public static void main(String[] args) throws IOException{ 74 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 75 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 76 | int n = Integer.parseInt(br.readLine()); 77 | String[] s; 78 | while(n-- > 0){ 79 | s = br.readLine().split(" "); 80 | int x = Integer.parseInt(s[1]); 81 | if(s[0].equals("I")){ 82 | insert(x); 83 | } 84 | else{ 85 | boolean flag = query(x); 86 | if(flag) bw.write("Yes\n"); 87 | else bw.write("No\n"); 88 | } 89 | } 90 | bw.close(); 91 | br.close(); 92 | } 93 | public static void insert(int x){ 94 | // 将 x 进行 hash 处理,+N 是为了防止负数取模仍然为负数 95 | int k = (x % N + N) % N; 96 | // 就是一个单链表,把 h[k] 当成头 97 | e[idx] = x; 98 | ne[idx] = h[k]; 99 | h[k] = idx; 100 | idx++; 101 | } 102 | public static boolean query(int x){ 103 | int k = (x % N + N) % N; 104 | // 从 h[k] 开始找,如果 i == 0,说明一条链表找完了 105 | for(int i = h[k]; i != 0; i = ne[i]){ 106 | if(e[i] == x) return true; 107 | } 108 | return false; 109 | } 110 | } 111 | ``` 112 | 113 | 2. 开放寻址法 114 | 115 | ```java 116 | import java.util.*; 117 | import java.io.*; 118 | 119 | public class Main{ 120 | // N 初始化 2~3 倍空间的第一个质数 121 | public static final int N = 200003; 122 | public static final int NULL = 0x3f3f3f3f; 123 | public static int[] h = new int[N]; 124 | public static void main(String[] args) throws IOException{ 125 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 126 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 127 | int n = Integer.parseInt(br.readLine()); 128 | String[] s; 129 | // 初始化一个比 x 上限大的值 130 | Arrays.fill(h, NULL); 131 | while(n-- > 0){ 132 | s = br.readLine().split(" "); 133 | int x = Integer.parseInt(s[1]); 134 | if(s[0].equals("I")){ 135 | int idx = find(x); 136 | h[idx] = x; 137 | } 138 | else{ 139 | if(h[find(x)] == NULL) bw.write("No\n"); 140 | else bw.write("Yes\n"); 141 | } 142 | } 143 | bw.close(); 144 | br.close(); 145 | } 146 | public static int find(int x){ 147 | int k = (x % N + N) % N; 148 | while(h[k] != NULL && h[k] != x){ 149 | k ++; 150 | if(k == N) k = 0; 151 | } 152 | return k; 153 | } 154 | } 155 | ``` 156 | 157 | ### yxc 158 | 159 | ![image-20210217161745736](pics/image-20210217161745736.png) -------------------------------------------------------------------------------- /2数据结构/AcWing841字符串哈希.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数据结构 2 | 3 | ## AcWing 841. 字符串哈希 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2,请你判断[l1,r1]和[l2,r2 10 | 11 | ]这两个区间所包含的字符串子串是否完全相同。 12 | 13 | 字符串中只包含大小写英文字母和数字。 14 | 15 | **输入格式** 16 | 17 | 第一行包含整数n和m,表示字符串长度和询问次数。 18 | 19 | 第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。 20 | 21 | 接下来m行,每行包含四个整数l1,r1,l2,r2,表示一次询问所涉及的两个区间。 22 | 23 | 注意,字符串的位置从1开始编号。 24 | 25 | **输出格式** 26 | 27 | 对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“Yes”,否则输出“No”。 28 | 29 | 每个结果占一行。 30 | 31 | **数据范围** 32 | 33 | $1≤n,m≤10^5$ 34 | 35 | ```r 36 | 输入样例: 37 | 38 | 8 3 39 | aabbaabb 40 | 1 3 5 7 41 | 1 3 6 8 42 | 1 2 1 2 43 | 44 | 输出样例: 45 | 46 | Yes 47 | No 48 | Yes 49 | ``` 50 | 51 | ### Solution 52 | 53 | ```java 54 | import java.util.*; 55 | import java.io.*; 56 | 57 | public class Main{ 58 | public static final int N = 100010; 59 | // 进制为 131 60 | public static final int P = 131; 61 | // h[k] 存储字符串前 k 个字母的哈希值 62 | public static long[] h = new long[N]; 63 | // p[k] 存储 P^k 64 | public static long[] p = new long[N]; 65 | public static long M = Long.MAX_VALUE; 66 | public static char[] sc; 67 | public static void main(String[] args) throws IOException{ 68 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 69 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 70 | String[] s = br.readLine().split(" "); 71 | int n = Integer.parseInt(s[0]); 72 | int m = Integer.parseInt(s[1]); 73 | sc = br.readLine().toCharArray(); 74 | p[0] = 1; 75 | for(int i = 1; i <= n; i++){ 76 | // h[i] = (h[i - 1] * P + sc[i - 1]) % M; 77 | // p[i] = (p[i - 1] * P) % M; 78 | h[i] = h[i - 1] * P + sc[i - 1]; 79 | p[i] = p[i - 1] * P; 80 | } 81 | while(m-- > 0){ 82 | s = br.readLine().split(" "); 83 | int l1 = Integer.parseInt(s[0]); 84 | int r1 = Integer.parseInt(s[1]); 85 | int l2 = Integer.parseInt(s[2]); 86 | int r2 = Integer.parseInt(s[3]); 87 | if(query(l1, r1) == query(l2, r2)) bw.write("Yes\n"); 88 | else bw.write("No\n"); 89 | } 90 | bw.close(); 91 | br.close(); 92 | } 93 | public static long query(int l, int r){ 94 | return h[r] - h[l - 1] * p [r - l + 1]; 95 | } 96 | } 97 | ``` 98 | 99 | ### yxc 100 | 101 | ![image-20210217192838725](pics/image-20210217192838725.png) 102 | 103 | ```cpp 104 | #include 105 | #include 106 | 107 | using namespace std; 108 | 109 | typedef unsigned long long ULL; 110 | 111 | const int N = 100010, P = 131; 112 | 113 | int n, m; 114 | char str[N]; 115 | ULL h[N], p[N]; 116 | 117 | ULL get(int l, int r) 118 | { 119 | return h[r] - h[l - 1] * p[r - l + 1]; 120 | } 121 | 122 | int main() 123 | { 124 | scanf("%d%d", &n, &m); 125 | scanf("%s", str + 1); 126 | 127 | p[0] = 1; 128 | for (int i = 1; i <= n; i ++ ) 129 | { 130 | h[i] = h[i - 1] * P + str[i]; 131 | p[i] = p[i - 1] * P; 132 | } 133 | 134 | while (m -- ) 135 | { 136 | int l1, r1, l2, r2; 137 | scanf("%d%d%d%d", &l1, &r1, &l2, &r2); 138 | 139 | if (get(l1, r1) == get(l2, r2)) puts("Yes"); 140 | else puts("No"); 141 | } 142 | 143 | return 0; 144 | } 145 | 146 | // 作者:yxc 147 | // 链接:https://www.acwing.com/activity/content/code/content/45313/ 148 | // 来源:AcWing 149 | // 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 150 | ``` 151 | 152 | -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217161426667.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217161426667.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217161745736.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217161745736.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217161943396.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217161943396.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217192838725.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217192838725.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217195208053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217195208053.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210217195854304.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210217195854304.png -------------------------------------------------------------------------------- /2数据结构/pics/image-20210218150532815.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinezzz/AcWingBasicAlgorithmCourse/2b9adb589befda66eb40fe7567fef167fc774bab/2数据结构/pics/image-20210218150532815.png -------------------------------------------------------------------------------- /3搜索与图论/AcWing842排列数字.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | ## AcWing 842. 排列数字 3 | 4 | `难度:简单` 5 | 6 | ### 题目描述 7 | 8 | 给定一个整数n,将数字1~n排成一排,将会有很多种排列方法。 9 | 10 | 现在,请你按照字典序将所有的排列方法输出。 11 | 12 | **输入格式** 13 | 14 | 共一行,包含一个整数n。 15 | 16 | **输出格式** 17 | 18 | 按字典序输出所有排列方案,每个方案占一行。 19 | 20 | **数据范围** 21 | 22 | $1≤n≤7$ 23 | 24 | ```r 25 | 输入样例: 26 | 27 | 3 28 | 29 | 输出样例: 30 | 31 | 1 2 3 32 | 1 3 2 33 | 2 1 3 34 | 2 3 1 35 | 3 1 2 36 | 3 2 1 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | import java.io.*; 44 | 45 | class Main{ 46 | public static int N = 10; 47 | public static int[] a = new int[N]; 48 | public static boolean[] flag = new boolean[N]; 49 | public static void main(String[] args) throws IOException{ 50 | Scanner sc = new Scanner(System.in); 51 | int n = sc.nextInt(); 52 | dfs(1, n); 53 | } 54 | public static void dfs(int u, int n){ 55 | // 满足结束条件,打印输出 56 | if(u == n + 1){ 57 | for(int i = 1; i <= n; i++){ 58 | System.out.print(a[i] + " "); 59 | } 60 | System.out.println(); 61 | return; 62 | } 63 | // 遍历选择列表 64 | for(int i = 1; i <= n; i++){ 65 | // 如果这个选项还没有选择 66 | if(!flag[i]){ 67 | // 标记 68 | flag[i] = true; 69 | // 选择放进路径 70 | a[u] = i; 71 | // 递归 72 | dfs(u + 1, n); 73 | // 恢复现场 74 | flag[i] = false; 75 | a[u] = 0; 76 | } 77 | } 78 | } 79 | } 80 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing843n皇后问题.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 843. n-皇后问题 4 | 5 | `难度:中等` 6 | 7 | ### 题目描述 8 | 9 | n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 10 | 11 | 现在给定整数n,请你输出所有的满足条件的棋子摆法。 12 | 13 | **输入格式** 14 | 15 | 共一行,包含整数n。 16 | 17 | **输出格式** 18 | 19 | 每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。 20 | 21 | 其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。 22 | 23 | 每个方案输出完成后,输出一个空行。 24 | 25 | 输出方案的顺序任意,只要不重复且没有遗漏即可。 26 | 27 | **数据范围** 28 | 29 | $1≤n≤9$ 30 | 31 | ```r 32 | 输入样例: 33 | 34 | 4 35 | 36 | 输出样例: 37 | 38 | .Q.. 39 | ...Q 40 | Q... 41 | ..Q. 42 | 43 | ..Q. 44 | Q... 45 | ...Q 46 | .Q.. 47 | ``` 48 | 49 | ### Solution 50 | 51 | ```java 52 | import java.util.*; 53 | import java.io.*; 54 | 55 | class Main{ 56 | public static int N = 15; 57 | // 存储结果 58 | public static char[][] res = new char[N][N]; 59 | // 标记是否冲突,列,主对角线,副对角线 60 | // 因为是一行一行递归,所以行不会有冲突 61 | public static boolean[] col = new boolean[N]; 62 | public static boolean[] dg = new boolean[2*N]; 63 | public static boolean[] sdg = new boolean[2*N]; 64 | public static void main(String[] args){ 65 | Scanner sc = new Scanner(System.in); 66 | int n = sc.nextInt(); 67 | for(int i = 0; i < n; i++){ 68 | for(int j = 0; j < n; j++){ 69 | res[i][j] = '.'; 70 | } 71 | } 72 | // DFS 的是行,从第 0 行开始 73 | dfs(0, n); 74 | } 75 | public static void dfs(int u, int n){ 76 | // 如果已经遍历完所有行,输出答案 77 | if(u == n){ 78 | for(int i = 0; i < n; i++){ 79 | for(int j = 0; j < n; j++) 80 | System.out.print(res[i][j]); 81 | System.out.println(); 82 | } 83 | System.out.println(); 84 | return; 85 | } 86 | // 遍历每一列,看是否可以放皇后 87 | for(int i = 0; i < n; i++){ 88 | // 主对角线和为定值,副对角线差为定值,防止差为负数,加个N 89 | if(!col[i] && !dg[u + i] && !sdg[u - i + N]){ 90 | col[i] = dg[u + i] = sdg[u - i + N] = true; 91 | res[u][i] = 'Q'; 92 | dfs(u + 1, n); 93 | res[u][i] = '.'; 94 | col[i] = dg[u + i] = sdg[u - i + N] = false; 95 | } 96 | } 97 | } 98 | } 99 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing844走迷宫.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 844. 走迷宫 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0或1,其中0表示可以走的路,1表示不可通过的墙壁。 10 | 11 | 最初,有一个人位于左上角(1, 1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。 12 | 13 | 请问,该人从左上角移动至右下角(n, m)处,至少需要移动多少次。 14 | 15 | 数据保证(1, 1)处和(n, m)处的数字为0,且一定至少存在一条通路。 16 | 17 | **输入格式** 18 | 19 | 第一行包含两个整数n和m。 20 | 21 | 接下来n行,每行包含m个整数(0或1),表示完整的二维数组迷宫。 22 | 23 | **输出格式** 24 | 25 | 输出一个整数,表示从左上角移动至右下角的最少移动次数。 26 | 27 | **数据范围** 28 | 29 | $1≤n,m≤100$ 30 | 31 | ```r 32 | 输入样例: 33 | 34 | 5 5 35 | 0 1 0 0 0 36 | 0 1 0 1 0 37 | 0 0 0 0 0 38 | 0 1 1 1 0 39 | 0 0 0 1 0 40 | 41 | 输出样例: 42 | 43 | 8 44 | ``` 45 | ### Solution 46 | 47 | ```java 48 | import java.util.*; 49 | 50 | class Main{ 51 | public static final int N = 110; 52 | // a 数组保存地图 53 | public static int[][] a = new int[N][N]; 54 | // d 数组表示从起点到 (x, y) 的距离,如没有走过为-1 55 | public static int[][] d = new int[N][N]; 56 | public static int[] dy = {0, -1, 0, 1}; 57 | public static int[] dx = {-1, 0, 1, 0}; 58 | public static void main(String[] args){ 59 | Scanner sc = new Scanner(System.in); 60 | int n = sc.nextInt(); 61 | int m = sc.nextInt(); 62 | for(int i = 1; i <= n; i++){ 63 | for(int j = 1; j <= m; j++){ 64 | a[i][j] = sc.nextInt(); 65 | } 66 | } 67 | // 从(1,1)走到(n,m) 68 | // 把起始点初始化为 1,这样 d[x][y] 为 0 的点就是没有到达的点 69 | d[1][1] = 1; 70 | Queue q = new ArrayDeque<>(); 71 | q.add(new Pair(1, 1)); 72 | while(!q.isEmpty()){ 73 | Pair p = q.poll(); 74 | // 朝四个方向走 75 | for(int i = 0; i < 4; i++){ 76 | int x = p.x + dx[i], y = p.y + dy[i]; 77 | // 如果(x,y)在范围内且没有障碍物,则代表可走,放入队列; 78 | if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == 0 && d[x][y] == 0){ 79 | d[x][y] = d[p.x][p.y] + 1; 80 | q.add(new Pair(x, y)); 81 | } 82 | } 83 | } 84 | System.out.print(d[n][m] - 1); 85 | } 86 | static class Pair{ 87 | int x, y; 88 | public Pair(int x, int y){ 89 | this.x = x; 90 | this.y = y; 91 | } 92 | } 93 | } 94 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing845八数码.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 845. 八数码 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 在一个3×3的网格中,1~8这8个数字和一个“x”恰好不重不漏地分布在这3×3的网格中。 10 | 11 | 例如: 12 | 13 | ```r 14 | 1 2 3 15 | x 4 6 16 | 7 5 8 17 | ``` 18 | 19 | 在游戏过程中,可以把“x”与其上、下、左、右四个方向之一的数字交换(如果存在)。 20 | 21 | 我们的目的是通过交换,使得网格变为如下排列(称为正确排列): 22 | 23 | ```r 24 | 1 2 3 25 | 4 5 6 26 | 7 8 x 27 | ``` 28 | 29 | 例如,示例中图形就可以通过让“x”先后与右、下、右三个方向的数字交换成功得到正确排列。 30 | 31 | 交换过程如下: 32 | 33 | ```r 34 | 1 2 3 1 2 3 1 2 3 1 2 3 35 | x 4 6 4 x 6 4 5 6 4 5 6 36 | 7 5 8 7 5 8 7 x 8 7 8 x 37 | ``` 38 | 39 | 现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。 40 | 41 | 42 | **输入格式** 43 | 44 | 输入占一行,将3×3的初始网格描绘出来。 45 | 46 | 例如,如果初始网格如下所示: 47 | 48 | ```r 49 | 1 2 3 50 | 51 | x 4 6 52 | 53 | 7 5 8 54 | 55 | 则输入为:1 2 3 x 4 6 7 5 8 56 | ``` 57 | 58 | **输出格式** 59 | 60 | 输出占一行,包含一个整数,表示最少交换次数。 61 | 62 | 如果不存在解决方案,则输出”-1”。 63 | 64 | ```r 65 | 输入样例: 66 | 67 | 2 3 4 1 5 x 7 6 8 68 | 69 | 输出样例 70 | 71 | 19 72 | ``` 73 | 74 | ### Solution 75 | 76 | 字符串处理差劲,这部分还要优化。 77 | 78 | ```java 79 | import java.util.*; 80 | 81 | class Main{ 82 | public static void main(String[] args){ 83 | Scanner sc = new Scanner(System.in); 84 | StringBuilder state = new StringBuilder(); 85 | for(int i = 0; i < 9; i++){ 86 | state.append(sc.next()); 87 | } 88 | System.out.println(bfs(state)); 89 | } 90 | public static int bfs(StringBuilder state){ 91 | String end = "12345678x"; 92 | Queue q = new ArrayDeque<>(); 93 | Map map = new HashMap<>(); 94 | map.put(state.toString(),0); 95 | q.add(state); 96 | 97 | int[] dx = {-1, 0, 1, 0}; 98 | int[] dy = {0, -1, 0, 1}; 99 | 100 | while(!q.isEmpty()){ 101 | StringBuilder t = q.poll(); 102 | if(t.toString().equals(end)){ 103 | return map.get(t.toString()); 104 | } 105 | int dist = map.get(t.toString()); 106 | int idx = t.indexOf("x"); 107 | // x 转化为九宫格的位置 108 | int x = idx / 3; 109 | int y = idx % 3; 110 | // 找到可以和 x 交换的位置 111 | for(int i = 0; i < 4; i++){ 112 | int a = x + dx[i], b = y + dy[i]; 113 | // 判断坐标是否在九宫格内 114 | if(a >= 0 && a < 3 && b >= 0 && b < 3){ 115 | swap(t, a * 3 + b, x * 3 + y); 116 | if(!map.containsKey(t.toString())){ 117 | map.put(t.toString(), dist + 1); 118 | q.add(new StringBuilder(t)); 119 | } 120 | swap(t, a * 3 + b, x * 3 + y); 121 | } 122 | } 123 | } 124 | return -1; 125 | } 126 | public static void swap(StringBuilder t, int m, int n){ 127 | char c = t.charAt(m); 128 | t.setCharAt(m, t.charAt(n)); 129 | t.setCharAt(n, c); 130 | } 131 | } 132 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing846树的重心.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 846. 树的重心 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。 10 | 11 | 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 12 | 13 | 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。 14 | 15 | **输入格式** 16 | 17 | 第一行包含整数n,表示树的结点数。 18 | 19 | 接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边。 20 | 21 | **输出格式** 22 | 23 | 输出一个整数m,表示将重心删除后,剩余各个连通块中点数的最大值。 24 | 25 | **数据范围** 26 | 27 | $1≤n≤10^5$ 28 | 29 | ```r 30 | 输入样例 31 | 32 | 9 33 | 1 2 34 | 1 7 35 | 1 4 36 | 2 8 37 | 2 5 38 | 4 3 39 | 3 9 40 | 4 6 41 | 42 | 输出样例: 43 | 44 | 4 45 | ``` 46 | 47 | ### Solution 48 | 49 | ```java 50 | import java.util.*; 51 | import java.io.*; 52 | 53 | class Main{ 54 | public static final int N = 100010; 55 | // 因为是无向边,所以要乘以 2 56 | public static int[] e = new int[N * 2]; 57 | public static int[] ne = new int[N * 2]; 58 | public static int[] h = new int[N * 2]; 59 | // idx 初始化为 1,这样指向 0 的时候就代表为空 60 | public static int idx = 1; 61 | // 标记该点是否遍历过 62 | public static boolean[] flag = new boolean[N]; 63 | // 记录答案,求最小值,初始化为最大 64 | public static int ans = N; 65 | public static void main(String[] args) throws IOException{ 66 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 67 | int n = Integer.parseInt(br.readLine()); 68 | String[] s; 69 | for(int i = 1; i <= n - 1; i++){ 70 | s = br.readLine().split(" "); 71 | int a = Integer.parseInt(s[0]); 72 | int b = Integer.parseInt(s[1]); 73 | // 建树 74 | add(a, b); 75 | add(b, a); 76 | } 77 | // 数据范围 1-100000,所以从 1 开始递归 78 | dfs(1, n); 79 | System.out.println(ans); 80 | } 81 | public static void add(int a, int b){ 82 | e[idx] = b; 83 | ne[idx] = h[a]; 84 | h[a] = idx; 85 | idx++; 86 | } 87 | public static int dfs(int u, int n){ 88 | flag[u] = true; 89 | int size = 0, sum = 0; 90 | for(int i = h[u]; i != 0; i = ne[i]){ 91 | int j = e[i]; 92 | if(flag[j]) continue; 93 | int s = dfs(j, n); 94 | // 找到子树中点数最多的 95 | size = Math.max(size, s); 96 | // 计算所有子树的点数的和 97 | sum += s; 98 | } 99 | // 在子树和子树外求一个最大值 100 | size = Math.max(size, n - sum - 1); 101 | // 求答案 102 | ans = Math.min(ans, size); 103 | return sum + 1; 104 | } 105 | } 106 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing847图中点的层次.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 847. 图中点的层次 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,图中可能存在重边和自环。 10 | 11 | 所有边的长度都是1,点的编号为1~n。 12 | 13 | 请你求出1号点到n号点的最短距离,如果从1号点无法走到n号点,输出-1。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m。 18 | 19 | 接下来m行,每行包含两个整数a和b,表示存在一条从a走到b的长度为1的边。 20 | 21 | **输出格式** 22 | 23 | 输出一个整数,表示1号点到n号点的最短距离。 24 | 25 | **数据范围** 26 | 27 | $1≤n,m≤10^5$ 28 | 29 | ```r 30 | 输入样例: 31 | 32 | 4 5 33 | 1 2 34 | 2 3 35 | 3 4 36 | 1 3 37 | 1 4 38 | 39 | 输出样例: 40 | 41 | 1 42 | ``` 43 | 44 | ### Solution 45 | 46 | ```java 47 | import java.util.*; 48 | import java.io.*; 49 | 50 | public class Main{ 51 | public static final int N = 100010; 52 | // 建图 53 | public static int[] e = new int[N]; 54 | public static int[] ne = new int[N]; 55 | public static int[] h = new int[N]; 56 | public static int idx = 1; 57 | // 记录 1 到 j 点的距离 58 | public static int[] dist = new int[N]; 59 | // 存储结果 60 | public static int res = 0; 61 | public static void main(String[] args) throws IOException{ 62 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 63 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 64 | String[] s = br.readLine().split(" "); 65 | int n = Integer.parseInt(s[0]); 66 | int m = Integer.parseInt(s[1]); 67 | while(m-- > 0){ 68 | s = br.readLine().split(" "); 69 | int a = Integer.parseInt(s[0]); 70 | int b = Integer.parseInt(s[1]); 71 | add(a, b); 72 | } 73 | bfs(); 74 | System.out.println(dist[n] - 1); 75 | } 76 | public static void add(int a, int b){ 77 | e[idx] = b; 78 | ne[idx] = h[a]; 79 | h[a] = idx; 80 | idx++; 81 | } 82 | public static void bfs(){ 83 | Queue q = new ArrayDeque<>(); 84 | q.add(1); 85 | // dist[1] 初始化为 1,这样只要碰到 0,就认为没有遍历过 86 | // 可以省去一个标记数组 87 | dist[1] = 1; 88 | 89 | while(!q.isEmpty()){ 90 | int t = q.remove(); 91 | for(int i = h[t]; i != 0; i = ne[i]){ 92 | int j = e[i]; 93 | 94 | if(dist[j] == 0){ 95 | q.add(j); 96 | dist[j] = dist[t] + 1; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing848有向图的拓扑序列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 848. 有向图的拓扑序列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,点的编号是1到n,图中可能存在重边和自环。 10 | 11 | 请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1。 12 | 13 | 若一个由图中所有点构成的序列A满足:对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m 18 | 19 | 接下来m行,每行包含两个整数x和y,表示存在一条从点x到点y的有向边(x, y)。 20 | 21 | **输出格式** 22 | 23 | 共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。 24 | 25 | 否则输出-1。 26 | 27 | **数据范围** 28 | 29 | $1≤n,m≤10^5$ 30 | 31 | ``` 32 | 输入样例: 33 | 34 | 3 3 35 | 1 2 36 | 2 3 37 | 1 3 38 | 39 | 输出样例: 40 | 41 | 1 2 3 42 | ``` 43 | 44 | ### Solution 45 | 46 | 有向无环图也叫拓扑图 47 | 48 | ```java 49 | import java.util.*; 50 | import java.io.*; 51 | 52 | class Main{ 53 | // 建图 54 | public static final int N = 100010; 55 | public static int[] e = new int[N]; 56 | public static int[] ne = new int[N]; 57 | public static int[] h = new int[N]; 58 | public static int idx = 1; 59 | // 数组模拟队列 60 | public static int[] q = new int[N]; 61 | // 记录每个点的入度 62 | public static int[] d = new int[N]; 63 | public static void main(String[] args) throws IOException{ 64 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 65 | String[] s = br.readLine().split(" "); 66 | int n = Integer.parseInt(s[0]); 67 | int m = Integer.parseInt(s[1]); 68 | while(m-- > 0){ 69 | s = br.readLine().split(" "); 70 | int a = Integer.parseInt(s[0]); 71 | int b = Integer.parseInt(s[1]); 72 | add(a, b); 73 | // 加入一条 a->b 边,b 的入度加 1 74 | d[b]++; 75 | } 76 | if(topsort(n)){ 77 | for(int i = 0; i < n; i++){ 78 | System.out.print(q[i] + " "); 79 | } 80 | } 81 | else System.out.println(-1); 82 | } 83 | public static void add(int a, int b){ 84 | e[idx] = b; 85 | ne[idx] = h[a]; 86 | h[a] = idx; 87 | idx++; 88 | } 89 | public static boolean topsort(int n){ 90 | // hh 队列头部;tt 队列尾部 91 | // 队列 头部出队,尾部入队 92 | int hh = 0, tt = -1; 93 | for(int i = 1; i <= n; i++){ 94 | // d[i] == 0 表示 i 的入度为 0,就把 i 入队 95 | if(d[i] == 0){ 96 | q[++tt] = i; 97 | } 98 | } 99 | while(hh <= tt){ 100 | int t = q[hh++]; 101 | for(int i = h[t]; i != 0; i = ne[i]){ 102 | int j = e[i]; 103 | if(--d[j] == 0) q[++tt] = j; 104 | } 105 | } 106 | // 队列从 0 开始,如果队尾的下标为 n - 1,表示遍历所有元素了 107 | return tt == n - 1; 108 | } 109 | } 110 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing849Dijkstra求最短路I.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 849. Dijkstra求最短路 I 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。 10 | 11 | 请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1。 12 | 13 | **输入格式** 14 | 15 | 第一行包含整数n和m。 16 | 17 | 接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。 18 | 19 | **输出格式** 20 | 21 | 输出一个整数,表示1号点到n号点的最短距离。 22 | 23 | 如果路径不存在,则输出-1。 24 | 25 | **数据范围** 26 | 27 | $1≤n≤500,$ 28 | $1≤m≤10^5,$ 29 | 30 | 图中涉及边长均不超过10000。 31 | 32 | ```r 33 | 输入样例: 34 | 35 | 3 3 36 | 1 2 2 37 | 2 3 1 38 | 1 3 4 39 | 40 | 输出样例: 41 | 42 | 3 43 | ``` 44 | 45 | ### Solution 46 | 47 | ```java 48 | import java.util.*; 49 | import java.io.*; 50 | 51 | class Main{ 52 | public static final int N = 510; 53 | public static final int INF = 0x3f3f3f3f; 54 | // 稠密图,用邻接矩阵来存 55 | public static int[][] g = new int[N][N]; 56 | public static int[] d = new int[N]; 57 | public static boolean[] flag = new boolean[N]; 58 | public static void main(String[] args) throws IOException{ 59 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 60 | String[] s = br.readLine().split(" "); 61 | int n = Integer.parseInt(s[0]); 62 | int m = Integer.parseInt(s[1]); 63 | // 初始化为 INF 64 | for(int i = 0; i < N; i++){ 65 | Arrays.fill(g[i], INF); 66 | } 67 | Arrays.fill(d, INF); 68 | while(m-- > 0){ 69 | s = br.readLine().split(" "); 70 | int x = Integer.parseInt(s[0]); 71 | int y = Integer.parseInt(s[1]); 72 | int z = Integer.parseInt(s[2]); 73 | // 如果有重边或者自环,就选较小的 74 | g[x][y] = Math.min(g[x][y], z); 75 | } 76 | System.out.println(dijkstra(n)); 77 | } 78 | public static int dijkstra(int n){ 79 | d[1] = 0; 80 | // 迭代 n 次 81 | // 每次找当前没有确定最短路长度的点当中距离最小的一个 82 | for(int i = 1; i <= n; i++){ 83 | int t = -1; 84 | // 遍历 1-n 个点,找到还没处理的且距离最小的 85 | for(int j = 1; j <= n; j++){ 86 | if(!flag[j] && (t == -1 || d[t] > d[j])) 87 | t = j; 88 | } 89 | // 找到 t,打标记 90 | flag[t] = true; 91 | // 已经找到了最短 d[t],更新一波 92 | for(int j = 1; j <= n; j++){ 93 | d[j] = Math.min(d[j], d[t] + g[t][j]); 94 | } 95 | } 96 | if(d[n] == 0x3f3f3f3f) return -1; 97 | return d[n]; 98 | } 99 | } 100 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing851spfa求最短路.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 851. spfa求最短路 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。 10 | 11 | 请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出impossible。 12 | 13 | 数据保证不存在负权回路。 14 | 15 | **输入格式** 16 | 17 | 第一行包含整数n和m。 18 | 19 | 接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。 20 | 21 | **输出格式** 22 | 23 | 输出一个整数,表示1号点到n号点的最短距离。 24 | 25 | 如果路径不存在,则输出”impossible”。 26 | 27 | **数据范围** 28 | 29 | $1≤n,m≤10^5,$ 30 | 31 | 图中涉及边长绝对值均不超过10000。 32 | 33 | ```r 34 | 输入样例: 35 | 36 | 3 3 37 | 1 2 5 38 | 2 3 -3 39 | 1 3 4 40 | 41 | 输出样例: 42 | 43 | 2 44 | ``` 45 | 46 | ### Solution 47 | 48 | ```java 49 | import java.util.*; 50 | import java.io.*; 51 | 52 | class Main{ 53 | static int INF = 0x3f3f3f3f; 54 | // 稀疏图用邻接表来存储 55 | static int N = 100010; 56 | static int[] e = new int[N]; 57 | static int[] ne = new int[N]; 58 | static int[] h = new int[N]; 59 | static int[] w = new int[N]; 60 | static int idx = 1; 61 | // 记录与起点的距离 62 | static int[] d = new int[N]; 63 | // 记录队列里是否已经有了 64 | static boolean[] flag = new boolean[N]; 65 | public static void add(int x, int y, int z){ 66 | e[idx] = y; 67 | w[idx] = z; 68 | ne[idx] = h[x]; 69 | h[x] = idx++; 70 | } 71 | public static int spfa(int n){ 72 | // 初始化 73 | Arrays.fill(d, INF); 74 | d[1] = 0; 75 | Queue q = new ArrayDeque<>(); 76 | q.add(1); 77 | flag[1] = true; 78 | while(!q.isEmpty()){ 79 | int t = q.remove(); 80 | flag[t] = false; 81 | // 遍历所有以 t 为出发点的边 82 | for(int i = h[t]; i != 0; i = ne[i]){ 83 | int j = e[i]; 84 | if(d[j] > d[t] + w[i]){ 85 | d[j] = d[t] + w[i]; 86 | // 如果队列中没有 j,就将 j 入队 87 | if(!flag[j]){ 88 | q.add(j); 89 | flag[j] = true; 90 | } 91 | } 92 | } 93 | } 94 | return d[n]; 95 | 96 | } 97 | public static void main(String[] args) throws IOException{ 98 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 99 | String[] s = br.readLine().split(" "); 100 | int n = Integer.parseInt(s[0]); 101 | int m = Integer.parseInt(s[1]); 102 | while(m-- > 0){ 103 | s = br.readLine().split(" "); 104 | int x = Integer.parseInt(s[0]); 105 | int y = Integer.parseInt(s[1]); 106 | int z = Integer.parseInt(s[2]); 107 | add(x, y, z); 108 | } 109 | if(spfa(n) < INF/2) System.out.println(d[n]); 110 | else System.out.println("impossible"); 111 | } 112 | 113 | } 114 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing853有边数限制的最短路.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 853. 有边数限制的最短路 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。 10 | 11 | 请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible。 12 | 13 | 注意:图中可能 存在负权回路 。 14 | 15 | **输入格式** 16 | 17 | 第一行包含三个整数n,m,k。 18 | 19 | 接下来m行,每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。 20 | 21 | **输出格式** 22 | 23 | 输出一个整数,表示从1号点到n号点的最多经过k条边的最短距离。 24 | 25 | 如果不存在满足条件的路径,则输出“impossible”。 26 | 27 | **数据范围** 28 | 29 | $1≤n,k≤500,$ 30 | $1≤m≤10000,$ 31 | 32 | 任意边长的绝对值不超过10000。 33 | 34 | ```r 35 | 输入样例: 36 | 37 | 3 3 1 38 | 1 2 1 39 | 2 3 1 40 | 1 3 3 41 | 42 | 输出样例: 43 | 44 | 3 45 | ``` 46 | 47 | ### Solution 48 | 49 | Bellman-Ford算法 50 | 51 | 时间复杂度 $O(nm)$, n 表示点数,m 表示边数 52 | 53 | **一般 spfa 性能比 Bellman-Ford 好,只有特殊情况下用 Bellman-Ford 算法,比如这题有边的数量的限制** 54 | 55 | 1. 思路 56 | ```r 57 | for n 次 58 | for 所有边 a,b,w 59 | dist[b] = min(dist[b], dist[a] + w) 60 | ``` 61 | 62 | 2. 解题代码 63 | 64 | ```java 65 | import java.util.*; 66 | import java.io.*; 67 | 68 | class Main{ 69 | // 稀疏图用邻接表来存储 70 | static int N = 510; 71 | static int M = 10010; 72 | // 存储所有边 73 | static Node[] e = new Node[M]; 74 | // 存储距离起点的距离 75 | static int[] d = new int[N]; 76 | // 备份 d 数组 77 | static int[] b = new int[N]; 78 | static int idx = 1; 79 | // 初始化值 80 | static final int INF = 0x3f3f3f3f; 81 | public static void main(String[] args) throws IOException{ 82 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 83 | String[] s = br.readLine().split(" "); 84 | int n = Integer.parseInt(s[0]); 85 | int m = Integer.parseInt(s[1]); 86 | int k = Integer.parseInt(s[2]); 87 | for(int i = 1; i <= m; i++){ 88 | s = br.readLine().split(" "); 89 | int x = Integer.parseInt(s[0]); 90 | int y = Integer.parseInt(s[1]); 91 | int z = Integer.parseInt(s[2]); 92 | e[i] = new Node(x, y, z); 93 | } 94 | bellmanFord(n, m, k); 95 | } 96 | public static void bellmanFord(int n, int m, int k){ 97 | Arrays.fill(d, INF); 98 | // 起点初始化为 0 99 | d[1] = 0; 100 | // 最多 k 条边,循环限制 k 次 101 | for(int i = 0; i < k; i++){ 102 | // 拷贝数组,否则会有串联问题,导致计算边的数量不准确 103 | b = Arrays.copyOf(d, N); 104 | for(int j = 1; j <= m; j++){ 105 | int x = e[j].x, y = e[j].y, z = e[j].z; 106 | d[y] = Math.min(d[y], b[x] + z); 107 | } 108 | } 109 | if(d[n] > INF / 2){ 110 | System.out.println("impossible"); 111 | }else{ 112 | System.out.println(d[n]); 113 | } 114 | } 115 | static class Node{ 116 | int x, y, z; 117 | public Node(int x, int y, int z){ 118 | this.x = x; 119 | this.y = y; 120 | this.z = z; 121 | } 122 | } 123 | } 124 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing854Floyd求最短路.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 854. Floyd求最短路 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。 10 | 11 | 再给定k个询问,每个询问包含两个整数x和y,表示查询从点x到点y的最短距离,如果路径不存在,则输出“impossible”。 12 | 13 | 数据保证图中不存在负权回路。 14 | 15 | **输入格式** 16 | 17 | 第一行包含三个整数n,m,k 18 | 19 | 接下来m行,每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。 20 | 21 | 接下来k行,每行包含两个整数x,y,表示询问点x到点y的最短距离。 22 | 23 | **输出格式** 24 | 25 | 共k行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出“impossible”。 26 | 27 | **数据范围** 28 | 29 | $1≤n≤200,$ 30 | $1≤k≤n^2$ 31 | $1≤m≤20000,$ 32 | 图中涉及边长绝对值均不超过10000。 33 | 34 | ```r 35 | 输入样例: 36 | 37 | 3 3 2 38 | 1 2 1 39 | 2 3 2 40 | 1 3 1 41 | 2 1 42 | 1 3 43 | 44 | 输出样例: 45 | 46 | impossible 47 | 1 48 | ``` 49 | 50 | ### Solution 51 | 52 | ```java 53 | import java.util.*; 54 | import java.io.*; 55 | import java.math.*; 56 | 57 | class Main{ 58 | static final int N = 210; 59 | static final int INF = 0x3f3f3f3f; 60 | static int[][] g = new int[N][N]; 61 | // floyd 算法 62 | public static void floyd(int n){ 63 | for(int k = 1; k <= n; k++) 64 | for(int i = 1; i <= n; i++) 65 | for(int j = 1; j <= n; j++) 66 | g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]); 67 | } 68 | public static void main(String[] args) throws IOException{ 69 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 70 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); 71 | String[] s = br.readLine().split(" "); 72 | int n = Integer.parseInt(s[0]); 73 | int m = Integer.parseInt(s[1]); 74 | int k = Integer.parseInt(s[2]); 75 | // 邻接矩阵初始化 76 | for (int i = 1; i <= n; i ++ ) 77 | for (int j = 1; j <= n; j ++ ) 78 | if (i == j) g[i][j] = 0; 79 | else g[i][j] = INF; 80 | while(m-- > 0){ 81 | s = br.readLine().split(" "); 82 | int x = Integer.parseInt(s[0]); 83 | int y = Integer.parseInt(s[1]); 84 | int z = Integer.parseInt(s[2]); 85 | g[x][y] = Math.min(g[x][y], z); 86 | } 87 | floyd(n); 88 | while(k-- > 0){ 89 | s = br.readLine().split(" "); 90 | int x = Integer.parseInt(s[0]); 91 | int y = Integer.parseInt(s[1]); 92 | if(g[x][y] > INF / 2) bw.write("impossible\n"); 93 | else bw.write(g[x][y] + "\n"); 94 | } 95 | bw.close(); 96 | br.close(); 97 | } 98 | } 99 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing858Prim算法求最小生成树.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 858. Prim算法求最小生成树 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。 10 | 11 | 求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。 12 | 13 | 给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。 14 | 15 | 由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。 16 | 17 | **输入格式** 18 | 19 | 第一行包含两个整数n和m。 20 | 21 | 接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。 22 | 23 | **输出格式** 24 | 25 | 共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。 26 | 27 | **数据范围** 28 | 29 | $1≤n≤500,$ 30 | $1≤m≤105,$ 31 | 图中涉及边的边权的绝对值均不超过10000。 32 | 33 | ```r 34 | 输入样例: 35 | 36 | 4 5 37 | 1 2 1 38 | 1 3 2 39 | 1 4 3 40 | 2 3 2 41 | 3 4 4 42 | 43 | 输出样例: 44 | 45 | 6 46 | ``` 47 | 48 | ### Solution 49 | 50 | ```java 51 | import java.util.*; 52 | import java.io.*; 53 | 54 | class Main{ 55 | static final int INF = 0x3f3f3f3f; 56 | static final int N = 510; 57 | // 邻接矩阵存图 58 | static int[][] g = new int[N][N]; 59 | // 记录 i 点到当前最小生成树的距离 60 | static int[] dist = new int[N]; 61 | // 标记 i 点是否已经在最小生成树中 62 | static boolean[] flag = new boolean[N]; 63 | // 存储最小生成树的距离 64 | static int res = 0; 65 | public static int prim(int n){ 66 | Arrays.fill(dist, INF); 67 | dist[1] = 0; 68 | // 迭代 n 次 69 | for(int i = 1; i <= n; i++){ 70 | // 找集合外距离集合最近的点 71 | // 用 t 来存储该点的序号 72 | int t = -1; 73 | for(int j = 1; j <= n; j++){ 74 | if(!flag[j] &&(t == -1 || dist[j] < dist[t])) 75 | t = j; 76 | } 77 | flag[t] = true; 78 | // 如果所有点距离集合的距离都为INF,即不可达,则说明没有最小生成树 79 | if(dist[t] == INF) return INF; 80 | res += dist[t]; 81 | // 更新所有点到集合的距离 82 | // 因为就新加了 t 点,所以就比较一下各个点到 t 的距离与原来的dist就行了 83 | // 注意这一句要放在 res += dist[t]之后 84 | // 因为如果有自环 dist[t] = Math.min(dist[t], g[t][t]);这样距离就变了 85 | for(int j = 1; j <= n; j++) dist[j] = Math.min(dist[j], g[t][j]); 86 | } 87 | return res; 88 | } 89 | 90 | public static void main(String[] args) throws IOException{ 91 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 92 | String[] s = br.readLine().split(" "); 93 | int n = Integer.parseInt(s[0]); 94 | int m = Integer.parseInt(s[1]); 95 | for(int i = 0; i < N; i++){ 96 | Arrays.fill(g[i], INF); 97 | } 98 | while(m-- > 0){ 99 | s = br.readLine().split(" "); 100 | int u = Integer.parseInt(s[0]); 101 | int v = Integer.parseInt(s[1]); 102 | int w = Integer.parseInt(s[2]); 103 | // 稠密图用邻接矩阵来存 104 | g[u][v] = g[v][u] = Math.min(g[u][v], w); 105 | } 106 | if(prim(n) == INF) System.out.println("impossible"); 107 | else System.out.println(res); 108 | 109 | } 110 | } 111 | 112 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing859Kruskal算法求最小生成树.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。 10 | 11 | 求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。 12 | 13 | 给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。 14 | 15 | 由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。 16 | 17 | **输入格式** 18 | 19 | 第一行包含两个整数n和m。 20 | 21 | 接下来m行,每行包含三个整数u,v,w,表示点u和点v之间存在一条权值为w的边。 22 | 23 | **输出格式** 24 | 25 | 共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。 26 | 27 | **数据范围** 28 | 29 | $1≤n≤10^5,$ 30 | $1≤m≤2∗10^5,$ 31 | 图中涉及边的边权的绝对值均不超过1000。 32 | 33 | ```r 34 | 输入样例: 35 | 36 | 4 5 37 | 1 2 1 38 | 1 3 2 39 | 1 4 3 40 | 2 3 2 41 | 3 4 4 42 | 43 | 输出样例: 44 | 45 | 6 46 | ``` 47 | 48 | ### Solution 49 | 50 | ```java 51 | import java.util.*; 52 | import java.io.*; 53 | 54 | class Main{ 55 | static final int N = 100010; 56 | static final int INF = 0x3f3f3f3f; 57 | // 并查集在 O(1) 复杂度来检测a,b是否连通 58 | static int[] p = new int[N]; 59 | // 最小生成树的长度 60 | static int res = 0; 61 | static class Edge{ 62 | int u, v, w; 63 | public Edge(int u, int v, int w){ 64 | this.u = u; 65 | this.v = v; 66 | this.w = w; 67 | } 68 | } 69 | public static int kruskal(int n, int m, Edge[] edges){ 70 | Arrays.sort(edges, (Edge e1, Edge e2) -> e1.w - e2.w); 71 | // 初始化并查集 72 | for(int i = 1; i <= n; i++) p[i] = i; 73 | int cnt = 0; 74 | for(int i = 0; i < m; i++){ 75 | int u = edges[i].u, v = edges[i].v, w = edges[i].w; 76 | // 并查集操作检查一条边的两个点是否连通 77 | // 找 u,v 的根节点 78 | u = find(u); 79 | v = find(v); 80 | // 两个连通块不连通,就合并 81 | if(u != v){ 82 | p[u] = v; 83 | res += w; 84 | cnt++; 85 | } 86 | } 87 | if(cnt < n - 1) return INF; 88 | else return res; 89 | } 90 | public static int find(int x){ 91 | if(p[x] != x) p[x] = find(p[x]); 92 | return p[x]; 93 | } 94 | public static void main(String[] args) throws IOException{ 95 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 96 | String[] s = br.readLine().split(" "); 97 | int n = Integer.parseInt(s[0]); 98 | int m = Integer.parseInt(s[1]); 99 | // 存边 100 | Edge[] edges = new Edge[m]; 101 | // 要排序还是从 0 开始 102 | for(int i = 0; i < m; i++){ 103 | s = br.readLine().split(" "); 104 | int u = Integer.parseInt(s[0]); 105 | int v = Integer.parseInt(s[1]); 106 | int w = Integer.parseInt(s[2]); 107 | // 枚举每条边,就不用邻接表了,直接一个数组 108 | edges[i] = new Edge(u, v, w); 109 | } 110 | 111 | int t = kruskal(n, m, edges); 112 | if(t == INF) System.out.println("impossible"); 113 | else System.out.println(res); 114 | } 115 | } 116 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing860染色法判定二分图.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 860. 染色法判定二分图 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个n个点m条边的无向图,图中可能存在重边和自环。 10 | 11 | 请你判断这个图是否是二分图。 12 | 13 | **输入格式** 14 | 15 | 第一行包含两个整数n和m。 16 | 17 | 接下来m行,每行包含两个整数u和v,表示点u和点v之间存在一条边。 18 | 19 | **输出格式** 20 | 21 | 如果给定图是二分图,则输出“Yes”,否则输出“No”。 22 | 23 | **数据范围** 24 | 25 | $1≤n,m≤10^5$ 26 | 27 | ```r 28 | 输入样例: 29 | 30 | 4 4 31 | 1 3 32 | 1 4 33 | 2 3 34 | 2 4 35 | 36 | 输出样例: 37 | 38 | Yes 39 | ``` 40 | 41 | ### Solution 42 | 43 | ```java 44 | import java.util.*; 45 | import java.io.*; 46 | 47 | class Main{ 48 | static final int N = 100010; 49 | static int[] e = new int[2 * N]; 50 | static int[] ne = new int[2 * N]; 51 | static int[] h = new int[N]; 52 | static int idx = 1; 53 | // 标记颜色 54 | static int[] color = new int[N]; 55 | public static void add(int u, int v){ 56 | e[idx] = v; 57 | ne[idx] = h[u]; 58 | h[u] = idx++; 59 | } 60 | // u 代表点的编号,c 代表颜色,值为1,2 61 | // dfs(u,c)表示把u号点染色成c颜色,并且判断从u号点开始染其他相连的点是否染成功 62 | public static boolean dfs(int u, int c){ 63 | color[u] = c; 64 | for(int i = h[u]; i != 0; i = ne[i]){ 65 | int j = e[i]; 66 | if(color[j] == 0){ 67 | // 还没染色,染上色,c是1就染成2,c是2就染成1,所以用3-c 68 | if(!dfs(j, 3 - c)) return false; 69 | } 70 | // 如果j已经染色,并且和u一样,就产生矛盾了 71 | else if(color[j] == c) return false; 72 | } 73 | return true; 74 | } 75 | public static void main(String[] args) throws IOException{ 76 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 77 | String[] s = br.readLine().split(" "); 78 | int n = Integer.parseInt(s[0]); 79 | int m = Integer.parseInt(s[1]); 80 | // 递归,用邻接表来存 81 | while(m-- > 0){ 82 | s = br.readLine().split(" "); 83 | int u = Integer.parseInt(s[0]); 84 | int v = Integer.parseInt(s[1]); 85 | add(u, v); 86 | add(v, u); 87 | } 88 | boolean flag = true; 89 | // 遍历 n 个点 90 | for(int i = 1; i <= n; i++){ 91 | // 如果没有染色,递归染色 92 | if(color[i] == 0){ 93 | // 如果返回flase,说明染色出现矛盾,就说明不是二分图 94 | if(!dfs(i, 1)){ 95 | flag = false; 96 | break; 97 | } 98 | } 99 | } 100 | if(flag) System.out.println("Yes"); 101 | else System.out.println("No"); 102 | } 103 | } 104 | ``` -------------------------------------------------------------------------------- /3搜索与图论/AcWing861二分图的最大匹配.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 搜索与图论 2 | 3 | ## AcWing 861. 二分图的最大匹配 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个二分图,其中左半部包含n1个点(编号1~n1),右半部包含n2个点(编号1~n2),二分图共包含m条边。 10 | 11 | 数据保证任意一条边的两个端点都不可能在同一部分中。 12 | 13 | 请你求出二分图的最大匹配数。 14 | 15 | > 二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。 16 | > 17 | > 二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。 18 | 19 | **输入格式** 20 | 21 | 第一行包含三个整数 n1、n2 和 m。 22 | 23 | 接下来m行,每行包含两个整数u和v,表示左半部点集中的点u和右半部点集中的点v之间存在一条边。 24 | 25 | **输出格式** 26 | 27 | 输出一个整数,表示二分图的最大匹配数。 28 | 29 | **数据范围** 30 | 31 | $1≤n1,n2≤500,$ 32 | $1≤u≤n1,$ 33 | $1≤v≤n2,$ 34 | $1≤m≤10^5$ 35 | 36 | ```r 37 | 输入样例: 38 | 39 | 2 2 4 40 | 1 1 41 | 1 2 42 | 2 1 43 | 2 2 44 | 45 | 输出样例: 46 | 47 | 2 48 | ``` 49 | 50 | ### Solution 51 | 52 | ```java 53 | import java.util.*; 54 | import java.io.*; 55 | 56 | public class Main{ 57 | static int N = 510; 58 | static int M = 100010; 59 | static int[] h = new int[N]; 60 | static int[] e = new int[M]; 61 | static int[] ne = new int[M]; 62 | static int idx; 63 | // match 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个 64 | static int[] match = new int[N]; 65 | // 表示第二个集合中的每个点是否已经被遍历过 66 | static boolean[] st = new boolean[N]; 67 | public static boolean find(int x){ 68 | for(int i = h[x]; i != -1; i = ne[i]){ 69 | int j = e[i]; 70 | if(!st[j]){ 71 | st[j] = true; 72 | //如果对方还未被匹配 或者 匹配的对象能找到下家匹配 73 | if(match[j] == 0 || find(match[j])){ 74 | match[j] = x; 75 | return true; 76 | } 77 | } 78 | } 79 | return false; 80 | } 81 | public static void add(int a, int b){ 82 | e[idx] = b; 83 | ne[idx] = h[a]; 84 | h[a] = idx++; 85 | } 86 | public static void main(String[] args) throws IOException{ 87 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 88 | String[] s = br.readLine().split(" "); 89 | int n1 = Integer.parseInt(s[0]); 90 | int n2 = Integer.parseInt(s[1]); 91 | int m = Integer.parseInt(s[2]); 92 | Arrays.fill(h, -1); 93 | while(m -- > 0){ 94 | s = br.readLine().split(" "); 95 | int a = Integer.parseInt(s[0]); 96 | int b = Integer.parseInt(s[1]); 97 | // 因为是从第二个集合中找符合第一个集合的匹配 98 | // 所以即使是无向图,存一条边就够了 99 | add(a, b); 100 | } 101 | int res = 0; 102 | for(int i = 1; i <= n1; i++){ 103 | Arrays.fill(st, false); 104 | if(find(i)){ 105 | res++; 106 | } 107 | } 108 | System.out.println(res); 109 | } 110 | } 111 | ``` -------------------------------------------------------------------------------- /4数学知识/4数学知识题目与模板.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | [TOC] 4 | 包括质数,约数,欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理,高斯消元,求组合数,容斥原理,博弈论等内容。 5 | 6 | ## 题目 7 | 8 | 质数 9 | - [x] AcWing 866. 试除法判定质数 10 | - [x] AcWing 867. 分解质因数 11 | - [x] AcWing 868. 筛质数 12 | 约数 13 | - [x] AcWing 869. 试除法求约数 14 | - [ ] AcWing 870. 约数个数 15 | - [ ] AcWing 871. 约数之和 16 | - [ ] AcWing 872. 最大公约数 17 | 欧拉函数 18 | - [ ] AcWing 873. 欧拉函数 19 | - [ ] AcWing 874. 筛法求欧拉函数 20 | 快速幂 21 | - [ ] AcWing 875. 快速幂 22 | - [ ] AcWing 876. 快速幂求逆元 23 | 扩展欧几里得算法 24 | - [ ] AcWing 877. 扩展欧几里得算法 25 | - [ ] AcWing 878. 线性同余方程 26 | 中国剩余定理 27 | - [ ] AcWing 204. 表达整数的奇怪方式 28 | 高斯消元 29 | - [ ] AcWing 883. 高斯消元解线性方程组 30 | - [ ] AcWing 884. 高斯消元解异或线性方程组 31 | 求组合数 32 | - [ ] AcWing 885. 求组合数 I 33 | - [ ] AcWing 886. 求组合数 II 34 | - [ ] AcWing 887. 求组合数 III 35 | - [ ] AcWing 888. 求组合数 IV 36 | - [ ] AcWing 889. 满足条件的01序列 37 | 容斥原理 38 | - [ ] AcWing 890. 能被整除的数 39 | 博弈论 40 | - [ ] AcWing 891. Nim游戏 41 | - [ ] AcWing 892. 台阶-Nim游戏 42 | - [ ] AcWing 893. 集合-Nim游戏 43 | - [ ] AcWing 894. 拆分-Nim游戏 44 | 45 | 46 | ## 模板 47 | 48 | 参考 AcWing [https://www.acwing.com/blog/content/406/](https://www.acwing.com/blog/content/406/) 49 | 50 | 1. 求组合数 51 | 52 | ||n 范围|a,b范围|方法|时间复杂度| 53 | |---|---|---|---|---| 54 | |1|10万|1 <= b <= a <= 2000|递推|N方| 55 | |2|1万|1 <= b <= a <= 10^5|预处理|NlogN| 56 | |3|20|1 <= b <= a <= 10^18|卢卡斯定理lucas|PlogNlogP| 57 | |4|10万|1 <= b <= a <= 2000|分解质因数| 58 | 59 | 60 | ### 试除法判定质数 —— 模板题 AcWing 866. 试除法判定质数 61 | 62 | 在大于 1 的整数中,如果只包含 1 和本身这两个约数,就被称为质数。 63 | 64 | 1. 模板:试除法 O(sprt(n)) 65 | 66 | ```cpp 67 | bool is_prime(int x) 68 | { 69 | if (x < 2) return false; 70 | // i <= x / i 很重要 71 | // i <= sqrt(n) 比较慢 72 | // i * i <= n 可能溢出 73 | for (int i = 2; i <= x / i; i ++ ) 74 | if (x % i == 0) 75 | return false; 76 | return true; 77 | } 78 | ``` 79 | 80 | ### 试除法分解质因数 —— 模板题 AcWing 867. 分解质因数 81 | 82 | 1. 模板:从小到大枚举所有 x 所有约数 O(sqrt(n)) 83 | ```cpp 84 | void divide(int x) 85 | { 86 | // n 中最多只包含一个大于 sqrt(n) 的质因子 87 | // 最后特殊处理一下 88 | for (int i = 2; i <= x / i; i ++ ) 89 | // 虽然i是所有数,但是 x%i==0 成立时 i 一定是质数 90 | if (x % i == 0) 91 | { 92 | int s = 0; 93 | while (x % i == 0) x /= i, s ++ ; 94 | cout << i << ' ' << s << endl; 95 | } 96 | // 如果最后 x 大于 1,说明 x 中存在大于 sqrt(n) 的质因子 97 | if (x > 1) cout << x << ' ' << 1 << endl; 98 | cout << endl; 99 | } 100 | ``` 101 | 102 | ### 朴素筛法求素数 —— 模板题 AcWing 868. 筛质数 103 | 104 | **质数定理: 1~n 中有 n/log(n) 个质数** 105 | 106 | 优化之后时间复杂度 O(nloglog(n)),近似为 O(n) 107 | 108 | 1. 模板:每个数被质数筛掉 109 | ```cpp 110 | int primes[N], cnt; // primes[]存储所有素数 111 | bool st[N]; // st[x]存储x是否被筛掉 112 | 113 | void get_primes(int n) 114 | { 115 | for (int i = 2; i <= n; i ++ ) 116 | { 117 | if (st[i]) continue; 118 | primes[cnt ++ ] = i; 119 | for (int j = i + i; j <= n; j += i) 120 | st[j] = true; 121 | } 122 | } 123 | ``` 124 | 125 | ### 线性筛法求素数 —— 模板题 AcWing 868. 筛质数 126 | 127 | 1. 模板:n 只会被最小质因子筛掉;对于合数 x,假设 pj 是 x 的最小质因子,当 i 枚举到 x/pj 的时候就会筛掉 128 | 129 | ```cpp 130 | int primes[N], cnt; // primes[]存储所有素数 131 | bool st[N]; // st[x]存储x是否被筛掉 132 | 133 | void get_primes(int n) 134 | { 135 | for (int i = 2; i <= n; i ++ ) 136 | { 137 | if (!st[i]) primes[cnt ++ ] = i; 138 | // 当 i 是合数时,枚举到 i 的最小质因子就会停下来 139 | // 当 i 是质数是,当 primes[j] == i 的时候也会停下来 140 | // 所以 j < cnt 没有必要 141 | for (int j = 0; primes[j] <= n / i; j ++ ) 142 | { 143 | st[primes[j] * i] = true; 144 | // primes[j] 从小到大来遍历 145 | // i % primes[j] == 0:pj 一定是 i 的最小质因子,pj 也一定是 pj * i 的最小质因子 146 | // i % primes[j] != 0:pj 一定小于 i 的所有质因子,所以 pj 也一定是 pj * i 的最小质因子 147 | if (i % primes[j] == 0) break; 148 | } 149 | } 150 | } 151 | ``` 152 | ### 试除法求所有约数 —— 模板题 AcWing 869. 试除法求约数 153 | 154 | 1. 模板: 155 | 156 | ```cpp 157 | vector get_divisors(int x) 158 | { 159 | vector res; 160 | for (int i = 1; i <= x / i; i ++ ) 161 | if (x % i == 0) 162 | { 163 | res.push_back(i); 164 | // 特判一下 165 | if (i != x / i) res.push_back(x / i); 166 | } 167 | sort(res.begin(), res.end()); 168 | return res; 169 | } 170 | ``` 171 | 172 | ### 约数个数和约数之和 —— 模板题 AcWing 870. 约数个数, AcWing 871. 约数之和 173 | 174 | 1. 模板 175 | 176 | ```java 177 | 如果 N = p1^c1 * p2^c2 * ... *pk^ck 178 | 约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1) 179 | 约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck) 180 | ``` 181 | 182 | ### 欧几里得算法 —— 模板题 AcWing 872. 最大公约数 183 | 184 | 1. 模板: 185 | 186 | ```cpp 187 | int gcd(int a, int b) 188 | { 189 | return b ? gcd(b, a % b) : a; 190 | } 191 | ``` 192 | 193 | ### 求欧拉函数 —— 模板题 AcWing 873. 欧拉函数 194 | 195 | 1. 模板: 196 | 197 | 1~N 中与 N 互质的数的个数被称为欧拉函数,记为$\phi(N)$ 198 | 199 | 若在算法基本定理中,$N = p_1^{a_1} p_2^{a_2}...p_m^{a_m}$,则:$\phi(N)=N * \frac{p_{1}-1}{p_{1}} * \frac{p_{2}-1}{p_{2}} * \ldots * \frac{p_{m}-1}{p_{m}}$ 200 | ```cpp 201 | int phi(int x) 202 | { 203 | int res = x; 204 | for (int i = 2; i <= x / i; i ++ ) 205 | if (x % i == 0) 206 | { 207 | res = res / i * (i - 1); 208 | while (x % i == 0) x /= i; 209 | } 210 | if (x > 1) res = res / x * (x - 1); 211 | 212 | return res; 213 | } 214 | ``` 215 | 216 | ### 筛法求欧拉函数 —— 模板题 AcWing 874. 筛法求欧拉函数 217 | 218 | 欧兰函数的应用:欧拉定理,若a 与 n 互质,$a^{\phi(n)} \text{mod} n \equiv 1$ 219 | 1. 模板 220 | 221 | ```cpp 222 | int primes[N], cnt; // primes[]存储所有素数 223 | int euler[N]; // 存储每个数的欧拉函数 224 | bool st[N]; // st[x]存储x是否被筛掉 225 | 226 | 227 | void get_eulers(int n) 228 | { 229 | euler[1] = 1; 230 | for (int i = 2; i <= n; i ++ ) 231 | { 232 | if (!st[i]) 233 | { 234 | primes[cnt ++ ] = i; 235 | // 如果一个数是质数的话,除了本身,其他所有数都和它互质,所以个数就是 i - 1 236 | euler[i] = i - 1; 237 | } 238 | for (int j = 0; primes[j] <= n / i; j ++ ) 239 | { 240 | int t = primes[j] * i; 241 | st[t] = true; 242 | if (i % primes[j] == 0) 243 | { 244 | // pj 是 i 的质因子,说明 i 的公式里已经包含了这个因子的表达 245 | // 根据公式,只需要再乘以 pj 246 | euler[t] = euler[i] * primes[j]; 247 | break; 248 | } 249 | // 如果 pj 不是 i 的质因子,根据公式如下 250 | euler[t] = euler[i] * (primes[j] - 1); 251 | } 252 | } 253 | } 254 | ``` 255 | ### 快速幂 —— 模板题 AcWing 875. 快速幂 256 | 257 | 求 $m^k mod p$,时间复杂度 $O(logk)$,很多应用。 258 | 259 | ```cpp 260 | int qmi(int m, int k, int p) 261 | { 262 | int res = 1 % p, t = m; 263 | while (k) 264 | { 265 | if (k&1) res = res * t % p; 266 | t = t * t % p; 267 | k >>= 1; 268 | } 269 | return res; 270 | } 271 | ``` 272 | 273 | ### 扩展欧几里得算法 —— 模板题 AcWing 877. 扩展欧几里得算法 274 | 275 | 裴蜀定理:对于任意正整数a,b,那么一定存在非零整数x,y,使得 ax + by = a 与 b 的最大公约数 276 | 277 | ```cpp 278 | // 求x, y,使得ax + by = gcd(a, b) 279 | int exgcd(int a, int b, int &x, int &y) 280 | { 281 | if (!b) 282 | { 283 | x = 1; y = 0; 284 | return a; 285 | } 286 | int d = exgcd(b, a % b, y, x); 287 | y -= (a/b) * x; 288 | return d; 289 | } 290 | ``` 291 | ### 高斯消元 —— 模板题 AcWing 883. 高斯消元解线性方程组 292 | 293 | ```cpp 294 | // a[N][N]是增广矩阵 295 | int gauss() 296 | { 297 | int c, r; 298 | for (c = 0, r = 0; c < n; c ++ ) 299 | { 300 | int t = r; 301 | for (int i = r; i < n; i ++ ) // 找到绝对值最大的行 302 | if (fabs(a[i][c]) > fabs(a[t][c])) 303 | t = i; 304 | 305 | if (fabs(a[t][c]) < eps) continue; 306 | 307 | for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端 308 | for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前上的首位变成1 309 | for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0 310 | if (fabs(a[i][c]) > eps) 311 | for (int j = n; j >= c; j -- ) 312 | a[i][j] -= a[r][j] * a[i][c]; 313 | 314 | r ++ ; 315 | } 316 | 317 | if (r < n) 318 | { 319 | for (int i = r; i < n; i ++ ) 320 | if (fabs(a[i][n]) > eps) 321 | return 2; // 无解 322 | return 1; // 有无穷多组解 323 | } 324 | 325 | for (int i = n - 1; i >= 0; i -- ) 326 | for (int j = i + 1; j < n; j ++ ) 327 | a[i][n] -= a[i][j] * a[j][n]; 328 | 329 | return 0; // 有唯一解 330 | } 331 | ``` 332 | 333 | ### 递归法求组合数 —— 模板题 AcWing 885. 求组合数 I 334 | 335 | ```cpp 336 | // c[a][b] 表示从a个苹果中选b个的方案数 337 | for (int i = 0; i < N; i ++ ) 338 | for (int j = 0; j <= i; j ++ ) 339 | if (!j) c[i][j] = 1; 340 | else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; 341 | 342 | 通过预处理逆元的方式求组合数 —— 模板题 AcWing 886. 求组合数 II 343 | 344 | 首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N] 345 | 如果取模的数是质数,可以用费马小定理求逆元 346 | int qmi(int a, int k, int p) // 快速幂模板 347 | { 348 | int res = 1; 349 | while (k) 350 | { 351 | if (k & 1) res = (LL)res * a % p; 352 | a = (LL)a * a % p; 353 | k >>= 1; 354 | } 355 | return res; 356 | } 357 | 358 | // 预处理阶乘的余数和阶乘逆元的余数 359 | fact[0] = infact[0] = 1; 360 | for (int i = 1; i < N; i ++ ) 361 | { 362 | fact[i] = (LL)fact[i - 1] * i % mod; 363 | infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod; 364 | } 365 | ``` 366 | 367 | ### Lucas定理 —— 模板题 AcWing 887. 求组合数 III 368 | 369 | ```cpp 370 | 若p是质数,则对于任意整数 1 <= m <= n,有: 371 | C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p) 372 | 373 | int qmi(int a, int k) // 快速幂模板 374 | { 375 | int res = 1; 376 | while (k) 377 | { 378 | if (k & 1) res = (LL)res * a % p; 379 | a = (LL)a * a % p; 380 | k >>= 1; 381 | } 382 | return res; 383 | } 384 | 385 | 386 | int C(int a, int b) // 通过定理求组合数C(a, b) 387 | { 388 | int res = 1; 389 | for (int i = 1, j = a; i <= b; i ++, j -- ) 390 | { 391 | res = (LL)res * j % p; 392 | res = (LL)res * qmi(i, p - 2) % p; 393 | } 394 | return res; 395 | } 396 | 397 | 398 | int lucas(LL a, LL b) 399 | { 400 | if (a < p && b < p) return C(a, b); 401 | return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p; 402 | } 403 | ``` 404 | 405 | ### 分解质因数法求组合数 —— 模板题 AcWing 888. 求组合数 IV 406 | 407 | ```cpp 408 | 当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用: 409 | 1. 筛法求出范围内的所有质数 410 | 2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ... 411 | 3. 用高精度乘法将所有质因子相乘 412 | 413 | int primes[N], cnt; // 存储所有质数 414 | int sum[N]; // 存储每个质数的次数 415 | bool st[N]; // 存储每个数是否已被筛掉 416 | 417 | 418 | void get_primes(int n) // 线性筛法求素数 419 | { 420 | for (int i = 2; i <= n; i ++ ) 421 | { 422 | if (!st[i]) primes[cnt ++ ] = i; 423 | for (int j = 0; primes[j] <= n / i; j ++ ) 424 | { 425 | st[primes[j] * i] = true; 426 | if (i % primes[j] == 0) break; 427 | } 428 | } 429 | } 430 | 431 | 432 | int get(int n, int p) // 求n!中的次数 433 | { 434 | int res = 0; 435 | while (n) 436 | { 437 | res += n / p; 438 | n /= p; 439 | } 440 | return res; 441 | } 442 | 443 | 444 | vector mul(vector a, int b) // 高精度乘低精度模板 445 | { 446 | vector c; 447 | int t = 0; 448 | for (int i = 0; i < a.size(); i ++ ) 449 | { 450 | t += a[i] * b; 451 | c.push_back(t % 10); 452 | t /= 10; 453 | } 454 | 455 | while (t) 456 | { 457 | c.push_back(t % 10); 458 | t /= 10; 459 | } 460 | 461 | return c; 462 | } 463 | 464 | get_primes(a); // 预处理范围内的所有质数 465 | 466 | for (int i = 0; i < cnt; i ++ ) // 求每个质因数的次数 467 | { 468 | int p = primes[i]; 469 | sum[i] = get(a, p) - get(b, p) - get(a - b, p); 470 | } 471 | 472 | vector res; 473 | res.push_back(1); 474 | 475 | for (int i = 0; i < cnt; i ++ ) // 用高精度乘法将所有质因子相乘 476 | for (int j = 0; j < sum[i]; j ++ ) 477 | res = mul(res, primes[i]); 478 | ``` 479 | 480 | ### 卡特兰数 —— 模板题 AcWing 889. 满足条件的01序列 481 | 482 | 483 | 给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: Cat(n) = C(2n, n) / (n + 1) 484 | 485 | NIM游戏 —— 模板题 AcWing 891. Nim游戏 486 | 487 | 给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。 488 | 489 | 我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。 490 | 所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。 491 | 492 | ### NIM博弈不存在平局,只有先手必胜和先手必败两种情况。 493 | 494 | 定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0 495 | 496 | ### 公平组合游戏ICG 497 | 498 | 若一个游戏满足: 499 | 500 | 由两名玩家交替行动; 501 | 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关; 502 | 不能行动的玩家判负; 503 | 504 | 则称该游戏为一个公平组合游戏。 505 | NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。 506 | 507 | ### 有向图游戏 508 | 509 | 给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。 510 | 任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。 511 | 512 | ### Mex运算 513 | 514 | 设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: 515 | mex(S) = min{x}, x属于自然数,且x不属于S 516 | 517 | ### SG函数 518 | 519 | 在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: 520 | SG(x) = mex({SG(y1), SG(y2), …, SG(yk)}) 521 | 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。 522 | 523 | ### 有向图游戏的和 —— 模板题 AcWing 893. 集合-Nim游戏 524 | 525 | 设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。 526 | 有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即: 527 | SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm) 528 | 529 | ### 定理 530 | 531 | 有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。 532 | 有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。 533 | -------------------------------------------------------------------------------- /4数学知识/AcWing866试除法判定质数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 866. 试除法判定质数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个正整数ai,判定每个数是否是质数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个正整数ai。 16 | 17 | **输出格式** 18 | 19 | 共n行,其中第 i 行输出第 i 个正整数ai 20 | 21 | 是否为质数,是则输出“Yes”,否则输出“No”。 22 | 23 | **数据范围** 24 | 25 | $1≤n≤100,$ 26 | $1≤ai≤2∗10^9$ 27 | 28 | ```r 29 | 输入样例: 30 | 31 | 2 32 | 2 33 | 6 34 | 35 | 输出样例: 36 | 37 | Yes 38 | No 39 | ``` 40 | 41 | ### Solution 42 | 43 | ```java 44 | import java.util.*; 45 | 46 | class Main{ 47 | public static boolean isPrime(int a){ 48 | if(a < 2) return false; 49 | // i <= x / i 很重要 50 | // i <= sqrt(n) 比较慢 51 | // i * i <= n 可能溢出 52 | for(int i = 2; i <= a / i; i++){ 53 | if(a % i == 0) return false; 54 | } 55 | return true; 56 | } 57 | public static void main(String[] args){ 58 | Scanner sc = new Scanner(System.in); 59 | int n = sc.nextInt(); 60 | while(n-- > 0){ 61 | int a = sc.nextInt(); 62 | if(isPrime(a)) System.out.println("Yes"); 63 | else System.out.println("No"); 64 | } 65 | } 66 | } 67 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing867分解质因数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 867. 分解质因数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个正整数ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个正整数ai。 16 | 17 | **输出格式** 18 | 19 | 对于每个正整数ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。 20 | 21 | 每个正整数的质因数全部输出完毕后,输出一个空行。 22 | 23 | **数据范围** 24 | 25 | $1≤n≤100,$ 26 | $1≤ai≤2∗10^9$ 27 | 28 | ```r 29 | 输入样例: 30 | 31 | 2 32 | 6 33 | 8 34 | 35 | 输出样例: 36 | 37 | 2 1 38 | 3 1 39 | 40 | 2 3 41 | ``` 42 | 43 | ### Solution 44 | 45 | ```java 46 | import java.util.*; 47 | 48 | class Main{ 49 | public static void divide(int a){ 50 | // i <= x / i 很重要 51 | // i <= sqrt(n) 比较慢 52 | // i * i <= n 可能溢出 53 | for(int i = 2; i <= a / i; i++){ 54 | if(a % i == 0){ 55 | int cnt = 0; 56 | while(a % i == 0){ 57 | a /= i; 58 | cnt++; 59 | } 60 | System.out.println(i + " " + cnt); 61 | } 62 | } 63 | // 一个数只会有一个大于 a/i 的质因数 64 | if(a > 1) System.out.println(a + " " + 1); 65 | System.out.println(); 66 | } 67 | public static void main(String[] args){ 68 | Scanner sc = new Scanner(System.in); 69 | int n = sc.nextInt(); 70 | while(n-- > 0){ 71 | int a = sc.nextInt(); 72 | divide(a); 73 | } 74 | } 75 | } 76 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing868筛质数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 868. 筛质数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个正整数n,请你求出1~n中质数的个数。 10 | 11 | **输入格式** 12 | 13 | 共一行,包含整数n。 14 | 15 | **输出格式** 16 | 17 | 共一行,包含一个整数,表示1~n中质数的个数。 18 | 19 | **数据范围** 20 | 21 | $1≤n≤10^6$ 22 | 23 | ```r 24 | 输入样例: 25 | 26 | 8 27 | 28 | 输出样例: 29 | 30 | 4 31 | ``` 32 | 33 | ### Solution 34 | 35 | 1. 埃氏筛法,时间复杂度O(nloglogn) 36 | 37 | **用质数筛去所有质数的倍数。** 38 | 39 | ```java 40 | import java.util.*; 41 | 42 | class Main{ 43 | public static int getPrime(int n){ 44 | int cnt = 0; 45 | // flag 表示 i 是否是合数 46 | boolean[] flag = new boolean[n + 10]; 47 | // 用质数来筛合数 48 | int[] p = new int[n + 10]; 49 | for(int i = 2; i <=n; i++){ 50 | if(flag[i]) continue; 51 | p[cnt++] = i; 52 | // i 是质数,筛掉 i 的所有倍数 53 | for(int j = i + i; j <= n; j = j + i){ 54 | flag[j] = true; 55 | } 56 | } 57 | return cnt; 58 | } 59 | public static void main(String[] args){ 60 | Scanner sc = new Scanner(System.in); 61 | int n = sc.nextInt(); 62 | int cnt = getPrime(n); 63 | System.out.println(cnt); 64 | } 65 | } 66 | ``` 67 | 68 | 2. 线性筛法,时间复杂度O(n) 69 | 70 | 每个合数只会被最小质数筛掉。 71 | 72 | ```java 73 | import java.util.*; 74 | 75 | class Main{ 76 | public static int getPrime(int n){ 77 | int cnt = 0; 78 | // flag 表示 i 是否是合数 79 | boolean[] flag = new boolean[n + 10]; 80 | // 用质数来筛合数 81 | int[] p = new int[n + 10]; 82 | for(int i = 2; i <=n; i++){ 83 | // 每个i都要遍历,不能跳过 84 | // 如果 i 是质数的花,加入数组 85 | if(!flag[i]) p[cnt++] = i; 86 | // i 是质数,筛掉 i 的所有倍数 87 | for(int j = 0; p[j] <= n / i; j ++){ 88 | // 用质数筛去合数 89 | flag[i * p[j]] = true; 90 | // i % p[j] == 0 表示 p[j] 是 i 的最小质因子 当然也是 i * p[j] 的最小质因子 91 | // 这个啥时候就要break了,否则就用不是最小的质因子来筛合数了 92 | // i % p[j] != 0 表示 p[j] 小于 i 的最小质因子,当然是 i * p[j] 的最小质因子 93 | if(i % p[j] == 0) break; 94 | } 95 | } 96 | return cnt; 97 | } 98 | public static void main(String[] args){ 99 | Scanner sc = new Scanner(System.in); 100 | int n = sc.nextInt(); 101 | int cnt = getPrime(n); 102 | System.out.println(cnt); 103 | } 104 | } 105 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing869试除法求约数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 869. 试除法求约数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个正整数ai,对于每个整数ai,请你按照从小到大的顺序输出它的所有约数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个整数ai。 16 | 17 | **输出格式** 18 | 19 | 输出共n行,其中第 i 行输出第 i 个整数ai的所有约数。 20 | 21 | **数据范围** 22 | 23 | $1≤n≤100,$ 24 | $2≤ai≤2∗10^9$ 25 | 26 | ```r 27 | 输入样例: 28 | 29 | 2 30 | 6 31 | 8 32 | 33 | 输出样例: 34 | 35 | 1 2 3 6 36 | 1 2 4 8 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | 44 | class Main{ 45 | public static void getDivisor(int a){ 46 | List p = new ArrayList<>(); 47 | for(int i = 1; i <= a / i; i++){ 48 | if(a % i == 0){ 49 | p.add(i); 50 | if(i != a / i) p.add(a / i); 51 | } 52 | } 53 | Collections.sort(p); 54 | for(int i = 0; i < p.size(); i++) 55 | System.out.print(p.get(i) + " "); 56 | System.out.println(); 57 | } 58 | public static void main(String[] args){ 59 | Scanner sc = new Scanner(System.in); 60 | int n = sc.nextInt(); 61 | while(n-- > 0){ 62 | int a = sc.nextInt(); 63 | getDivisor(a); 64 | } 65 | } 66 | } 67 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing870约数个数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 870. 约数个数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对$10^9+7$取模。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个整数ai。 16 | 17 | **输出格式** 18 | 19 | 输出一个整数,表示所给正整数的乘积的约数个数,答案需对$10^9+7$取模。 20 | 21 | **数据范围** 22 | 23 | $1≤n≤100,$ 24 | $1≤ai≤2∗10^9$ 25 | 26 | ``` 27 | 输入样例: 28 | 29 | 3 30 | 2 31 | 6 32 | 8 33 | 34 | 输出样例: 35 | 36 | 12 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | 44 | class Main{ 45 | static final int MOD =1000000007; 46 | static Map map = new HashMap<>(); 47 | public static void prime(int a){ 48 | for(int i = 2; i <= a / i; i++){ 49 | while(a % i == 0){ 50 | a /= i; 51 | map.put(i, map.getOrDefault(i, 0) + 1); 52 | } 53 | } 54 | if(a > 1) map.put(a, map.getOrDefault(a, 0) + 1); 55 | } 56 | public static void main(String[] args){ 57 | Scanner sc = new Scanner(System.in); 58 | int n = sc.nextInt(); 59 | for(int i = 0; i < n; i++){ 60 | int a = sc.nextInt(); 61 | prime(a); 62 | } 63 | long res = 1; 64 | for (int key : map.keySet()){ 65 | res = res * (map.get(key) + 1) % MOD; 66 | } 67 | System.out.println(res); 68 | 69 | } 70 | } 71 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing871约数之和.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 871. 约数之和 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个正整数ai,请你输出这些数的乘积的约数之和,答案对$10^9+7$取模。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个整数ai。 16 | 17 | **输出格式** 18 | 19 | 输出一个整数,表示所给正整数的乘积的约数个数,答案需对$10^9+7$取模。 20 | 21 | **数据范围** 22 | 23 | $1≤n≤100,$ 24 | $1≤ai≤2∗10^9$ 25 | 26 | ``` 27 | 输入样例: 28 | 29 | 3 30 | 2 31 | 6 32 | 8 33 | 34 | 输出样例: 35 | 36 | 252 37 | ``` 38 | 39 | 40 | ### Solution 41 | 42 | ```java 43 | import java.util.*; 44 | 45 | class Main{ 46 | static final int MOD =1000000007; 47 | static Map map = new HashMap<>(); 48 | public static void prime(int a){ 49 | for(int i = 2; i <= a / i; i++){ 50 | while(a % i == 0){ 51 | a /= i; 52 | map.put(i, map.getOrDefault(i, 0) + 1); 53 | } 54 | } 55 | if(a > 1) map.put(a, map.getOrDefault(a, 0) + 1); 56 | } 57 | public static void main(String[] args){ 58 | Scanner sc = new Scanner(System.in); 59 | int n = sc.nextInt(); 60 | for(int i = 0; i < n; i++){ 61 | int a = sc.nextInt(); 62 | prime(a); 63 | } 64 | long res = 1; 65 | for (int key : map.keySet()){ 66 | long c = 1; 67 | for(int i = 0; i < map.get(key); i++){ 68 | c = (c * key + 1) % MOD; 69 | } 70 | res = res * c % MOD; 71 | } 72 | System.out.println(res); 73 | } 74 | } 75 | ``` -------------------------------------------------------------------------------- /4数学知识/AcWing872最大公约数.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 数学知识 2 | 3 | ## AcWing 872. 最大公约数 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n对正整数ai,bi,请你求出每对数的最大公约数。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数n。 14 | 15 | 接下来n行,每行包含一个整数对ai,bi。 16 | 17 | **输出格式** 18 | 19 | 输出共n行,每行输出一个整数对的最大公约数。 20 | 21 | **数据范围** 22 | 23 | $1≤n≤10^5,$ 24 | $1≤ai,bi≤2∗10^9$ 25 | 26 | ```r 27 | 输入样例: 28 | 29 | 2 30 | 3 6 31 | 4 6 32 | 33 | 输出样例: 34 | 35 | 3 36 | 2 37 | ``` 38 | 39 | ### Solution 40 | 41 | ```java 42 | import java.util.*; 43 | 44 | class Main{ 45 | public static int gcd(int a, int b){ 46 | return b > 0 ? gcd(b, a % b) : a; 47 | } 48 | public static void main(String[] args){ 49 | Scanner sc = new Scanner(System.in); 50 | int n = sc.nextInt(); 51 | while(n-- > 0){ 52 | int a = sc.nextInt(); 53 | int b = sc.nextInt(); 54 | System.out.println(gcd(a, b)); 55 | } 56 | } 57 | } 58 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing282石子合并.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 282. 石子合并 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 设有N堆石子排成一排,其编号为1,2,3,…,N。 10 | 11 | 每堆石子有一定的质量,可以用一个整数来描述,现在要将这N堆石子合并成为一堆。 12 | 13 | 每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。 14 | 15 | 例如有4堆石子分别为 1 3 5 2, 我们可以先合并1、2堆,代价为4,得到4 5 2, 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24; 16 | 17 | 如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22。 18 | 19 | 问题是:找出一种合理的方法,使总的代价最小,输出最小代价。 20 | 21 | **输入格式** 22 | 23 | 第一行一个数N表示石子的堆数N。 24 | 25 | 第二行N个数,表示每堆石子的质量(均不超过1000)。 26 | 27 | **输出格式** 28 | 29 | 输出一个整数,表示最小代价。 30 | 31 | **数据范围** 32 | 33 | 1≤N≤300 34 | 35 | ```r 36 | 输入样例: 37 | 38 | 4 39 | 1 3 5 2 40 | 41 | 输出样例: 42 | 43 | 22 44 | ``` 45 | 46 | ### Solution 47 | 48 | ```java 49 | import java.util.*; 50 | 51 | class Main{ 52 | public static void main(String[] args){ 53 | Scanner sc = new Scanner(System.in); 54 | int N = sc.nextInt(); 55 | int INF = 0x3f3f3f3f; 56 | int[] a = new int[N + 10]; 57 | for(int i = 1; i <= N; i++) a[i] = sc.nextInt(); 58 | int[][] dp = new int[N + 10][N + 10]; 59 | // 先算一下前缀和 s 60 | int[] s = new int[N + 10]; 61 | for(int i = 1; i <= N; i++) s[i] = s[i - 1] + a[i]; 62 | // 状态表示:dp[i][j] 表示合并 i 到 j 的集合 63 | // 状态计算:(i,j) 取其中一点 k,可以分成两段 (i,k) 和 (k+1,j),dp[i][j] = dp[i][k] + dp[k+1][j] + s[j] - s[i-1] 64 | // 从相邻 2 个合并,一直到所有合并 65 | // 区间 dp,第一重 循环区间长度,第二重 循环左端点,根据区间长度和左端点确定右端点 66 | for(int len = 2; len <= N; len++){ 67 | for(int i = 1; i + len - 1 <= N; i++){ 68 | int j = i + len - 1; 69 | dp[i][j] = INF; 70 | for(int k = i; k < j; k++){ 71 | dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]); 72 | } 73 | } 74 | } 75 | System.out.println(dp[1][N]); 76 | } 77 | } 78 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing285没有上司的舞会.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 285. 没有上司的舞会 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 10 | Ural大学有N名职员,编号为1~N。 11 | 12 | 他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。 13 | 14 | 每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。 15 | 16 | 现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。 17 | 18 | 在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。 19 | 20 | **输入格式** 21 | 22 | 第一行一个整数N。 23 | 24 | 接下来N行,第 i 行表示 i 号职员的快乐指数Hi。 25 | 26 | 接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。 27 | 28 | **输出格式** 29 | 30 | 输出最大的快乐指数。 31 | 32 | **数据范围** 33 | 34 | 1≤N≤6000, 35 | 36 | −128≤Hi≤127 37 | 38 | ```r 39 | 输入样例: 40 | 41 | 7 42 | 1 43 | 1 44 | 1 45 | 1 46 | 1 47 | 1 48 | 1 49 | 1 3 50 | 2 3 51 | 6 4 52 | 7 4 53 | 4 5 54 | 3 5 55 | 56 | 输出样例: 57 | 58 | 5 59 | ``` 60 | 61 | ### Solution 62 | 63 | ```java 64 | import java.util.*; 65 | 66 | class Main{ 67 | static int N = 6010; 68 | // w[i] i 号职员的快乐指数 69 | static int[] w = new int[N]; 70 | // 建树 71 | static int[] e = new int[N]; 72 | static int[] ne = new int[N]; 73 | static int[] h = new int[N]; 74 | static int idx = 1; 75 | // 记录有没有父节点 76 | static boolean[] hasF = new boolean[N]; 77 | // 添加一条 a 指向 b 的边 78 | public static void add(int a, int b){ 79 | e[idx] = b; 80 | ne[idx] = h[a]; 81 | h[a] = idx++; 82 | } 83 | public static void dfs(int u, int[][] dp){ 84 | // 不需要递归结束条件 85 | dp[u][1] = w[u]; 86 | for(int i = h[u]; i != 0; i = ne[i]){ 87 | int j = e[i]; 88 | // 递归到叶子节点,然后往树根走 89 | dfs(j, dp); 90 | dp[u][0] += Math.max(dp[j][0], dp[j][1]); 91 | dp[u][1] += dp[j][0]; 92 | } 93 | } 94 | public static void main(String[] args){ 95 | Scanner sc = new Scanner(System.in); 96 | int n = sc.nextInt(); 97 | for(int i = 1; i <= n; i++) w[i] = sc.nextInt(); 98 | // 建树 99 | for(int i = 1; i < n; i++) { 100 | int l = sc.nextInt(); 101 | int k = sc.nextInt(); 102 | // k 是 l 的上司,添加一条 k 指向 l 的边 103 | add(k, l); 104 | hasF[l] = true; 105 | } 106 | // 找根节点 107 | int root = 1; 108 | while(hasF[root]) root++; 109 | // 从根节点开始递归 110 | // 状态表示: 111 | // dp[u][0] 表示是不偷 u 的最大值 112 | // dp[u][1] 表示偷 u 的最大值 113 | // 状态计算: 114 | // 遍历当前节点的所有子节点 115 | // for(int i = h[u]; i != 0; i = ne[i]) j = e[i] 116 | // dp[i][0] += Math.max(dp[j][0], dp[j][1]) 117 | // dp[i][1] += dp[j][0] 118 | // 初始化 119 | // dp[u][1] = w[u] 120 | int[][] dp = new int[N][2]; 121 | dfs(root, dp); 122 | System.out.println(Math.max(dp[root][0], dp[root][1])); 123 | } 124 | } 125 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing2_01背包问题.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 2. 01背包问题 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 10 | 11 | 第 i 件物品的体积是 vi,价值是 wi。 12 | 13 | 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 14 | 输出最大价值。 15 | 16 | **输入格式**: 17 | 18 | 第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。 19 | 20 | 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。 21 | 22 | **输出格式**: 23 | 24 | 输出一个整数,表示最大价值。 25 | 26 | **数据范围**: 27 | 28 | - $0 < N,V ≤ 1000$ 29 | - $0 一维dp数组 45 | 46 | **链接**: 47 | > 48 | 49 | ### Solution 50 | 51 | 52 | **思路**: 53 | 54 | - 状态:`dp[i][j]`表示只看前`i`个物品,总体积不超过`j`的情况下,背包中物品的最大价值 55 | 56 | 最后答案就是`dp[N][V]` 57 | 58 | - 状态转移:找最后一个不同点,也就是选最后一个物品的不同方案。对于`dp[i][j]`有两种情况: 59 | 60 | 1. 不选择当前的第件物品/第i件物品比背包容量要大 61 | 62 | 则`dp[i][j] = dp[i-1][j]` 63 | 64 | 2. 选择当前的第i件物品(潜在要求第i件物品体积小于等于背包总容量,即`v[i] >= j`) 65 | 66 | 则`dp[i][j] = dp[i-1][j-v[i]] + w[i]` 67 | 68 | 上面两种情况取max作为当前的最优解。 69 | 70 | - 边界:`dp[0][0] = 0` 71 | 72 | - **注意一些细节:** 73 | 74 | 1. 如果` j `表示体积正好是` j `的话,那么答案就需要遍历求`max`。如果表示的是 不超过` j `的话,答案就是` dp[N][V] `。 75 | 2. 如果题目要求背包必须放满,那么 `dp[0] [0…V]` 中仅仅有` dp[0][0]` 为0,其余值需被设置为`-INF`,返回dp[V]。 76 | 3. 如果并没有要求必须把背包装满,而是只希望总价值尽量大,初始化时应该将`dp[0...V ]`全部设为0, 返回dp[V]。 77 | 4. `i`和`j`从1开始循环。 78 | 79 | **普通版**: 80 | 81 | ```java 82 | import java.util.*; 83 | 84 | class Main{ 85 | public static void main(String[] args){ 86 | // 输入数据 87 | Scanner scan = new Scanner(System.in); 88 | int N = scan.nextInt(); 89 | int V = scan.nextInt(); 90 | 91 | int[] v = new int[N + 10]; 92 | int[] w = new int[N + 10]; 93 | for(int i = 1; i <= N; i++){ 94 | v[i] = scan.nextInt(); 95 | w[i] = scan.nextInt(); 96 | } 97 | scan.close(); 98 | 99 | // 开始dp 100 | int[][] dp = new int[N + 10][V + 10]; 101 | for(int i = 1; i <= N; i++){ 102 | for(int j = 1; j <= V; j++){ 103 | // 放进去和不放进去取最大值 104 | dp[i][j] = dp[i - 1][j]; 105 | if(j >= v[i]) 106 | dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]); 107 | 108 | } 109 | } 110 | // int res = 0; 111 | // for(int i = 0; i <= V; i++) res = Math.max(res, dp[N][i]); 112 | // System.out.println(res); 113 | // 或者 114 | System.out.println(dp[N][V]); 115 | } 116 | } 117 | ``` 118 | 119 | **进阶版**: 120 | 121 | 1. `dp[j]`表示体积不超过`j`的情况下的最大价值。 122 | 2. `dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i])`,看`j`,第二维要不就是用`j`,要不是就是用比`j`小的数。 123 | 3. 如果j从小往大循环,后面的dp[j]可能已经被前面的更新了,相当于dp[i][j - v[i]] 124 | 4. 所以让`j`从大到小循环。把第一维去掉,变成了`dp[j] = max(dp[j], dp[j-v[i] + w[i])`,比如计算第二层的时候,`dp[j-v[i]]`还没有在第二层被更新过(因为`j-v[i]`比`j`小),所以这个时候的`dp[j-v[i]]`存的是上一层的状态,也就是`dp[i-1][j-v[i]]` 125 | 126 | ```java 127 | import java.util.*; 128 | 129 | class Main{ 130 | public static void main(String[] args){ 131 | // 输入数据 132 | Scanner scan = new Scanner(System.in); 133 | int N = scan.nextInt(); 134 | int V = scan.nextInt(); 135 | int[] v = new int[N + 10]; 136 | int[] w = new int[N + 10]; 137 | for(int i = 1; i <= N; i++){ 138 | v[i] = scan.nextInt(); 139 | w[i] = scan.nextInt(); 140 | } 141 | scan.close(); 142 | // 开始dp 143 | int[] dp = new int[V + 10]; 144 | for(int i = 1; i <= N; i++){ 145 | for(int j = V; j >= v[i]; j--){ 146 | // 放进去和不放进去取最大值 147 | dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]); 148 | } 149 | } 150 | // 如果初始化:dp[0]为0,其他为负无穷,就要遍历取最大值。 151 | // 这种情况对应体积恰好为V的价值,保证所有的状态从0转移来 152 | // 153 | // 如果初始化:dp[i]都为0,dp[V]也是最大值。假设dp[k]为最大值。 154 | // dp[k]从0转移过来,那dp[V]从dp[V-k]转移过来,是一样的 155 | System.out.println(dp[V]); 156 | } 157 | } 158 | ``` 159 | -------------------------------------------------------------------------------- /5动态规划/AcWing3完全背包问题.md: -------------------------------------------------------------------------------- 1 | # LeetCode 专题 -- 背包专题 2 | 3 | ## AcWing 3. 完全背包问题 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。 10 | 11 | 第 i 种物品的体积是 vi,价值是 wi。 12 | 13 | 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 14 | 输出最大价值。 15 | 16 | **输入格式** 17 | 18 | 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。 19 | 20 | 接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。 21 | 22 | **输出格式** 23 | 24 | 输出一个整数,表示最大价值。 25 | 26 | **数据范围**: 27 | 28 | - $0 < N,V ≤ 1000$ 29 | - $0 < vi,wi ≤ 1000$ 30 | 31 | ```r 32 | 输入样例 33 | 4 5 34 | 1 2 35 | 2 4 36 | 3 4 37 | 4 5 38 | 输出样例: 39 | 10 40 | ``` 41 | 42 | **进阶**: 43 | 44 | > 一维dp数组 45 | 46 | **链接**: 47 | > 48 | 49 | ### Solution 50 | 51 | 52 | > **结论**: 53 | > 54 | > 将0-1背包中j的循环顺序改成从小到大,就变成了完全背包。 55 | > 56 | > 0-1背包:`dp[i][j] = max(dp[i-1][j], dp[i-1][j-vi]+wi)` 57 | > 58 | > 完全背包:`dp[i][j] = max(dp[i-1][j], dp[i][j-vi]+wi)` 59 | 60 | - 状态:`dp[i][j]`表示只看前`i`种物品,总体积不超过`j`的情况下,背包中物品的最大价值 61 | 62 | - 状态转移方程: 63 | 64 | 和0-1背包的区别在于每种物品有无限个,则划分子集的时候不再是分成两个子集(取和不取第i件物品),而是分成多个子集(不取第i件,第i件取1个,第i件取2个,...) 65 | 66 | 推导: 67 | 68 | `dp[i][j] = max(dp[i-1][j], dp[i-1][j-vi]+wi, dp[i-1][j-2vi]+2wi,...)` 69 | 70 | 把`j`用`j-vi`替代,可得 71 | 72 | `dp[i][j-vi] = max(dp[i-1][j-vi], dp[i-1][j-2vi]+wi, dp[i-1][j-3vi]+2wi,...)` 73 | 74 | 则`dp[i][j] = max(dp[i-1][j], dp[i][j-vi]+wi)` 75 | 76 | **普通版**: 77 | 78 | ```java 79 | import java.util.*; 80 | 81 | public class Main{ 82 | public static void main(String[] args){ 83 | Scanner scan = new Scanner(System.in); 84 | int N = scan.nextInt(); 85 | int V = scan.nextInt(); 86 | 87 | int[] v = new int[N + 10]; 88 | int[] w = new int[N + 10]; 89 | for(int i = 1; i <= N; i++){ 90 | v[i] = scan.nextInt(); 91 | w[i] = scan.nextInt(); 92 | } 93 | scan.close(); 94 | // 开始dp 95 | int[][] dp = new int[N + 10][V + 10]; 96 | for(int i = 1; i <= N; i++){ 97 | for(int j = 0; j <= V; j++){ 98 | dp[i][j] = dp[i - 1][j]; 99 | if(j >= v[i]) 100 | dp[i][j] = Math.max(dp[i][j], dp[i][j - v[i]] + w[i]); 101 | } 102 | } 103 | System.out.println(dp[N][V]); 104 | } 105 | } 106 | ``` 107 | 108 | **进阶版**: 109 | 110 | ```java 111 | import java.util.*; 112 | 113 | public class Main{ 114 | public static void main(String[] args){ 115 | Scanner scan = new Scanner(System.in); 116 | int N = scan.nextInt(); 117 | int V = scan.nextInt(); 118 | int[] v = new int[N + 10]; 119 | int[] w = new int[N + 10]; 120 | for(int i = 1; i <= N; i++){ 121 | v[i] = scan.nextInt(); 122 | w[i] = scan.nextInt(); 123 | } 124 | scan.close(); 125 | // 开始dp 126 | int[] dp = new int[V + 10]; 127 | for(int i = 1; i <= N; i++){ 128 | for(int j = v[i]; j <= V; j++){ 129 | dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]); 130 | } 131 | } 132 | System.out.println(dp[V]); 133 | } 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /5动态规划/AcWing4多重背包问题I.md: -------------------------------------------------------------------------------- 1 | # LeetCode 专题 -- 背包专题 2 | 3 | ## AcWing 4. 多重背包问题 I 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 有 N 种物品和一个容量是 V 的背包。 10 | 11 | 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 12 | 13 | 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 14 | 输出最大价值。 15 | 16 | **输入格式**: 17 | 18 | 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。 19 | 20 | 接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。 21 | 22 | **输出格式** 23 | 输出一个整数,表示最大价值。 24 | 25 | **数据范围**: 26 | 27 | - 0 < N,V ≤ 100 28 | - 0 < vi,wi,si ≤ 100 29 | 30 | ```matlab 31 | 输入样例 32 | 4 5 33 | 1 2 3 34 | 2 4 1 35 | 3 4 3 36 | 4 5 2 37 | 输出样例: 38 | 10 39 | ``` 40 | 41 | **进阶**: 42 | 43 | > 一维dp数组 44 | 45 | **链接**: 46 | > 47 | 48 | ### Solution 49 | 50 | 51 | **普通版**: 52 | 53 | ```java 54 | import java.util.*; 55 | 56 | class Main{ 57 | public static void main(String[] args){ 58 | Scanner sc = new Scanner(System.in); 59 | int N = sc.nextInt(); 60 | int V = sc.nextInt(); 61 | int[] v = new int[N + 10]; 62 | int[] w = new int[N + 10]; 63 | int[] s = new int[N + 10]; 64 | for(int i = 1; i <= N; i++){ 65 | v[i] = sc.nextInt(); 66 | w[i] = sc.nextInt(); 67 | s[i] = sc.nextInt(); 68 | } 69 | int[][] dp = new int[N + 10][V + 10]; 70 | for(int i = 1; i <= N; i++){ 71 | for(int j = 1; j <= V; j++){ 72 | dp[i][j] = dp[i - 1][j]; 73 | // 前面跟01背包一样 74 | // 01背包可以看成每种只有1个的多重背包 75 | // 那多重背包就是遍历 k 个, 76 | // 循环条件是 k <= s[i] && k * v[i] <= j 77 | for(int k = 1; k <= s[i] && k * v[i] <= j; k++){ 78 | dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]); 79 | } 80 | } 81 | } 82 | System.out.println(dp[N][V]); 83 | } 84 | } 85 | ``` 86 | 87 | **进阶版**: 88 | 89 | ```java 90 | import java.util.*; 91 | 92 | class Main{ 93 | public static void main(String[] args){ 94 | Scanner sc = new Scanner(System.in); 95 | int N = sc.nextInt(); 96 | int V = sc.nextInt(); 97 | int[] v = new int[N + 10]; 98 | int[] w = new int[N + 10]; 99 | int[] s = new int[N + 10]; 100 | for(int i = 1; i <= N; i++){ 101 | v[i] = sc.nextInt(); 102 | w[i] = sc.nextInt(); 103 | s[i] = sc.nextInt(); 104 | } 105 | int[] dp = new int[V + 10]; 106 | for(int i = 1; i <= N; i++){ 107 | for(int j = V; j >= v[i]; j--){ 108 | for(int k = 0; k <= s[i] && k * v[i] <= j; k++){ 109 | dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]); 110 | } 111 | } 112 | } 113 | System.out.println(dp[V]); 114 | } 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /5动态规划/AcWing5多重背包问题II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 专题 -- 背包专题 2 | 3 | ## AcWing 5. 多重背包问题 II 4 | 5 | `难度:中等` 6 | 7 | ### 题目描述 8 | 9 | 有 N 种物品和一个容量是 V 的背包。 10 | 11 | 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 12 | 13 | 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 14 | 15 | 输出最大价值。 16 | 17 | **输入格式** 18 | 19 | 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。 20 | 21 | 接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。 22 | 23 | **输出格式** 24 | 输出一个整数,表示最大价值。 25 | 26 | **数据范围**: 27 | 28 | - $0 < N ≤ 1000$ 29 | - $0 < V ≤ 2000$ 30 | - $0 < vi,wi,si ≤ 2000$ 31 | 32 | ```r 33 | 输入样例 34 | 4 5 35 | 1 2 3 36 | 2 4 1 37 | 3 4 3 38 | 4 5 2 39 | 输出样例: 40 | 10 41 | ``` 42 | 43 | **提示**: 44 | 45 | 本题考查多重背包的二进制优化方法。 46 | 47 | **进阶**: 48 | 49 | > 一维dp数组 50 | 51 | **链接**: 52 | > 53 | 54 | ### Solution 55 | 56 | 数据范围为1000,所以`O(N*V*s)`的复杂度会超时。 57 | 58 | 因此通过二进制优化降低复杂度。 59 | 60 | **二进制优化**: 61 | 62 | 比如$10 =2^0 + 2^1 + 2^2+3$ 63 | 64 | 将多重背包问题转化为0-1背包问题。即通过二进制转化的方式,将物品数量转化为多种物品数量的组合。比如假设物品有10个,即可转化为`1,2,4,3`,这四种无论如何组合,组合成的状态都在10以内,就可以转化为0-1背包问题。 65 | 66 | **重点是理解二进制优化的思想**: 67 | 68 | ```java 69 | import java.util.*; 70 | 71 | class Main{ 72 | static int M = 11010; 73 | public static void main(String[] args){ 74 | Scanner sc = new Scanner(System.in); 75 | int N = sc.nextInt(); 76 | int V = sc.nextInt(); 77 | int M = 11 * N + 10; 78 | int[] v = new int[M]; 79 | int[] w = new int[M]; 80 | int idx = 0; 81 | // 二进制处理 82 | // 比如 10 可以拆分为 1,2,4,3 83 | // 用二进制拆分,最后不够的单独一项 84 | // 转化为 01 背包问题 85 | for(int i = 1; i <= N; i++){ 86 | int a = sc.nextInt(); 87 | int b = sc.nextInt(); 88 | int s = sc.nextInt(); 89 | int k = 1; 90 | while(k <= s){ 91 | idx++; 92 | v[idx] = k * a; 93 | w[idx] = k * b; 94 | s -= k; 95 | k = k << 1; 96 | } 97 | if(s > 0){ 98 | idx++; 99 | v[idx] = s * a; 100 | w[idx] = s * b; 101 | } 102 | } 103 | int[] dp = new int[V + 10]; 104 | for(int i = 1; i <= idx; i++){ 105 | for(int j = V; j >= v[i]; j--){ 106 | dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]); 107 | } 108 | } 109 | System.out.println(dp[V]); 110 | } 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /5动态规划/AcWing895最长上升子序列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 895. 最长上升子序列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数N。 14 | 15 | 第二行包含N个整数,表示完整序列。 16 | 17 | **输出格式** 18 | 19 | 输出一个整数,表示最大长度。 20 | 21 | **数据范围** 22 | 23 | $1≤N≤1000,$ 24 | $−10^9≤数列中的数≤10^9$ 25 | 26 | ```r 27 | 输入样例: 28 | 29 | 7 30 | 3 1 2 1 8 5 6 31 | 32 | 输出样例: 33 | 34 | 4 35 | ``` 36 | 37 | ### Solution 38 | 39 | ```java 40 | import java.util.*; 41 | 42 | class Main{ 43 | public static void main(String[] args){ 44 | Scanner sc = new Scanner(System.in); 45 | int N = sc.nextInt(); 46 | int[] a = new int[N + 10]; 47 | int[] dp = new int[N + 10]; 48 | // 全部初始化为 1,因为每个字母都是一个上升子序列 49 | Arrays.fill(dp, 1); 50 | // N 的范围是 1000,可以用 n 方复杂度的做法 51 | // 两层循环 52 | // 状态表示: dp[i] 表示以 a[i] 结尾的上升子序列的长度;属性:最大值 53 | // 状态计算: 考虑倒数第二数字是否比当前数字小 54 | // 如果是 dp[i] = Math.max(dp[i],dp[j] + 1); 55 | for(int i = 1; i <= N; i++){ 56 | a[i] = sc.nextInt(); 57 | for(int j = 1; j <= i; j++){ 58 | if(a[j] < a[i]) dp[i] = Math.max(dp[i], dp[j] + 1); 59 | } 60 | } 61 | // 遍历一遍 62 | int res = 1; 63 | for(int d : dp) res = Math.max(res, d); 64 | System.out.println(res); 65 | } 66 | } 67 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing896最长上升子序列II.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 896. 最长上升子序列II 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。 10 | 11 | **输入格式** 12 | 13 | 第一行包含整数N。 14 | 15 | 第二行包含N个整数,表示完整序列。 16 | 17 | **输出格式** 18 | 19 | 输出一个整数,表示最大长度。 20 | 21 | **数据范围** 22 | 23 | $1≤N≤100000,$ 24 | $−10^9≤数列中的数≤10^9$ 25 | 26 | ```r 27 | 输入样例: 28 | 29 | 7 30 | 3 1 2 1 8 5 6 31 | 32 | 输出样例: 33 | 34 | 4 35 | ``` 36 | 37 | ### Solution 38 | 39 | ```java 40 | import java.util.*; 41 | 42 | class Main{ 43 | public static void main(String[] args){ 44 | Scanner sc = new Scanner(System.in); 45 | int N = sc.nextInt(); 46 | // N 为 100000, n 方的做法会 TLE 47 | int[] a = new int[N + 10]; 48 | // res[i] 数组记录长度为 i 时,所有子序列中结尾最小的元素 49 | int[] q = new int[N + 10]; 50 | // 初始化为 0 51 | int len = 0; 52 | for(int i = 1; i <= N; i++) { 53 | a[i] = sc.nextInt(); 54 | // 二分查找优化时间复杂度 logn 55 | // 查找最后一个小于 a[i] 的值 56 | int idx = bsearch(q, len, a[i]); 57 | if(idx == len) { 58 | len++; 59 | q[len] = a[i]; 60 | } 61 | else{ 62 | if(q[idx + 1] > a[i]) q[idx + 1] = a[i]; 63 | } 64 | } 65 | System.out.println(len); 66 | 67 | } 68 | public static int bsearch(int[] q, int n, int x){ 69 | // 找最后一个小于 x 的位置 70 | // 如果都比 x 大的话返回 0 71 | // 从 1 到 n 开始二分 72 | int low = 1, high = n; 73 | while(low <= high){ 74 | int mid = (low + high) / 2; 75 | if(q[mid] < x){ 76 | if(mid == n || q[mid + 1] >= x) return mid; 77 | else low = mid + 1; 78 | } 79 | else high = mid - 1; 80 | } 81 | return 0; 82 | } 83 | } 84 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing897最长公共子序列.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 897. 最长公共子序列 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。 10 | 输入格式 11 | 12 | 第一行包含两个整数N和M。 13 | 14 | 第二行包含一个长度为N的字符串,表示字符串A。 15 | 16 | 第三行包含一个长度为M的字符串,表示字符串B。 17 | 18 | 字符串均由小写字母构成。 19 | 20 | **输出格式** 21 | 22 | 输出一个整数,表示最大长度。 23 | 24 | **数据范围** 25 | 26 | 1≤N,M≤1000 27 | 28 | ```r 29 | 输入样例: 30 | 31 | 4 5 32 | acbd 33 | abedc 34 | 35 | 输出样例: 36 | 37 | 3 38 | ``` 39 | 40 | ### Solution 41 | 42 | ```java 43 | import java.util.*; 44 | 45 | class Main{ 46 | public static void main(String[] args){ 47 | Scanner sc = new Scanner(System.in); 48 | int N = sc.nextInt(); 49 | int M = sc.nextInt(); 50 | String a = sc.next(); 51 | String b = sc.next(); 52 | int[][] dp = new int[N + 10][M + 10]; 53 | // 状态表示:dp[i][j]代表S1中的前i个字符,S2中的前j个字符的最长公共子序列 54 | // 状态转移: 55 | // 如果S1[i]==S2[j],那么最长公共子序列必然包含S1[i]/S2[j]这个元素,可以先找到不包含S1[i]和S2[j]的最长公共子序列,再加上这个元素即可,不包含S1[i]和S2[j]的最长公共子序列就是f[i-1][j-1] 56 | // 如果S1[i]!=S2[j],最长的公共子序列中可能不包含S1[i](dp[i-1][j]),也可能不包含S2[j](dp[i][j-1]),也可能都不包含dp[i-1][j-1] 57 | for(int i = 1; i <= N; i++){ 58 | for(int j = 1; j <= M; j++){ 59 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 60 | if(a.charAt(i - 1) == b.charAt(j - 1)) dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); 61 | } 62 | } 63 | System.out.println(dp[N][M]); 64 | } 65 | } 66 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing898数字三角形.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 898. 数字三角形 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。 10 | 11 | ```r 12 | 7 13 | 3 8 14 | 8 1 0 15 | 2 7 4 4 16 | 4 5 2 6 5 17 | ``` 18 | 19 | **输入格式** 20 | 21 | 第一行包含整数n,表示数字三角形的层数。 22 | 23 | 接下来n行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。 24 | 25 | **输出格式** 26 | 27 | 输出一个整数,表示最大的路径数字和。 28 | 29 | **数据范围** 30 | 31 | 1≤n≤500, 32 | −10000≤三角形中的整数≤10000 33 | 34 | ```r 35 | 输入样例: 36 | 37 | 5 38 | 7 39 | 3 8 40 | 8 1 0 41 | 2 7 4 4 42 | 4 5 2 6 5 43 | 44 | 输出样例: 45 | 46 | 30 47 | ``` 48 | 49 | ### Solution 50 | 51 | 1. 自顶向下动态规划 52 | 53 | ```java 54 | import java.util.*; 55 | 56 | class Main{ 57 | public static void main(String[] args){ 58 | Scanner sc = new Scanner(System.in); 59 | int n = sc.nextInt(); 60 | int INF = 0x3f3f3f3f; 61 | int[][] a = new int[n + 10][n + 10]; 62 | int[][] dp = new int[n + 10][n + 10]; 63 | // 整数中有负数,dp 数组还是初始化为负无穷,第 0 行初始化为0就行了 64 | for(int i = 1; i <= n; i++){ 65 | Arrays.fill(dp[i], -INF); 66 | for(int j = 1; j <= i; j++) 67 | a[i][j] = sc.nextInt(); 68 | } 69 | // 自顶向下 70 | // 状态表示:集合:dp[i][j] 自顶向下到达 ij 的路径和;属性:最大值 71 | // 状态计算:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j]; 72 | for(int i = 1; i <= n; i++){ 73 | for(int j = 1; j <= i; j++) 74 | dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j]; 75 | } 76 | int res = -INF; 77 | for(int k = 0; k <= n; k++) res = Math.max(res, dp[n][k]); 78 | System.out.println(res); 79 | } 80 | } 81 | ``` 82 | 83 | **优化为一阶 DP 数组** 84 | 85 | ```java 86 | import java.util.*; 87 | 88 | class Main{ 89 | public static void main(String[] args){ 90 | Scanner sc = new Scanner(System.in); 91 | int n = sc.nextInt(); 92 | int INF = 0x3f3f3f3f; 93 | int[][] a = new int[n + 10][n + 10]; 94 | int[] dp = new int[n + 10]; 95 | for(int i = 1; i <= n; i++){ 96 | for(int j = 1; j <= i; j++) 97 | a[i][j] = sc.nextInt(); 98 | } 99 | // 初始化注意 100 | Arrays.fill(dp, -INF); 101 | dp[1] = a[1][1]; 102 | // 自顶向下 103 | // 状态表示:集合:dp[i][j] 自顶向下到达 ij 的路径和;属性:最大值 104 | // 状态计算:dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j]; 105 | for(int i = 2; i <= n; i++){ 106 | for(int j = i; j >= 1; j--) 107 | dp[j] = Math.max(dp[j], dp[j - 1]) + a[i][j]; 108 | } 109 | int res = -INF; 110 | for(int k = 0; k <= n; k++) res = Math.max(res, dp[k]); 111 | System.out.println(res); 112 | } 113 | } 114 | ``` 115 | 116 | 2. 自底向上动态规划 117 | 118 | ```java 119 | import java.util.*; 120 | 121 | class Main{ 122 | public static void main(String[] args){ 123 | Scanner sc = new Scanner(System.in); 124 | int n = sc.nextInt(); 125 | int INF = 0x3f3f3f3f; 126 | int[][] a = new int[n + 10][n + 10]; 127 | int[][] dp = new int[n + 10][n + 10]; 128 | // 整数中有负数,dp 数组还是初始化为负无穷,第 0 行初始化为0就行了 129 | for(int i = 1; i <= n; i++){ 130 | Arrays.fill(dp[i], -INF); 131 | for(int j = 1; j <= i; j++) 132 | a[i][j] = sc.nextInt(); 133 | } 134 | // 自底向上 135 | // 状态表示:集合:dp[i][j] 自底向上到达 ij 的路径和;属性:最大值 136 | // 状态计算:dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j]; 137 | for(int i = n; i >= 1; i--){ 138 | for(int j = 1; j <= i; j++) 139 | dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j]; 140 | } 141 | System.out.println(dp[1][1]); 142 | } 143 | } 144 | ``` 145 | 146 | 优化为一维 DP 数组 147 | 148 | ```java 149 | import java.util.*; 150 | 151 | class Main{ 152 | public static void main(String[] args){ 153 | Scanner sc = new Scanner(System.in); 154 | int n = sc.nextInt(); 155 | int INF = 0x3f3f3f3f; 156 | int[][] a = new int[n + 10][n + 10]; 157 | int[] dp = new int[n + 10]; 158 | // dp 数组初始化为 0 159 | for(int i = 1; i <= n; i++){ 160 | for(int j = 1; j <= i; j++) 161 | a[i][j] = sc.nextInt(); 162 | } 163 | // 自底向上 164 | // 状态表示:集合:dp[i][j] 自底向上到达 ij 的路径和;属性:最大值 165 | // 状态计算:dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j]; 166 | for(int i = n; i >= 1; i--){ 167 | for(int j = 1; j <= i; j++) 168 | dp[j] = Math.max(dp[j], dp[j + 1]) + a[i][j]; 169 | } 170 | System.out.println(dp[1]); 171 | } 172 | } 173 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing899编辑距离.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 899. 编辑距离 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定n个长度不超过10的字符串以及m次询问,每次询问给出一个字符串和一个操作次数上限。 10 | 11 | 对于每次询问,请你求出给定的n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。 12 | 13 | 每个对字符串进行的单个字符的插入、删除或替换算作一次操作。 14 | 15 | **输入格式** 16 | 17 | 第一行包含两个整数n和m。 18 | 19 | 接下来n行,每行包含一个字符串,表示给定的字符串。 20 | 21 | 再接下来m行,每行包含一个字符串和一个整数,表示一次询问。 22 | 23 | 字符串中只包含小写字母,且长度均不超过10。 24 | 25 | **输出格式** 26 | 27 | 输出共m行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。 28 | 29 | **数据范围** 30 | 31 | 1≤n,m≤1000, 32 | 33 | ```r 34 | 输入样例: 35 | 36 | 3 2 37 | abc 38 | acd 39 | bcd 40 | ab 1 41 | acbd 2 42 | 43 | 输出样例: 44 | 45 | 1 46 | 3 47 | ``` 48 | 49 | ### Solution 50 | 51 | ```java 52 | import java.util.*; 53 | 54 | class Main{ 55 | public static void main(String[] args){ 56 | Scanner sc = new Scanner(System.in); 57 | int n = sc.nextInt(); 58 | int m = sc.nextInt(); 59 | String[] s = new String[n + 10]; 60 | String[] q = new String[m + 10]; 61 | int[] num = new int[m + 10]; 62 | for(int i = 1; i <= n; i++) s[i] = sc.next(); 63 | for(int i = 1; i <= m; i++){ 64 | q[i] = sc.next(); 65 | num[i] = sc.nextInt(); 66 | int cnt = 0; 67 | for(int j = 1; j <= n; j++){ 68 | if(find(s[j], q[i]) <= num[i]) cnt++; 69 | } 70 | System.out.println(cnt); 71 | } 72 | } 73 | public static int find(String a, String b){ 74 | int n = a.length(), m = b.length(); 75 | int[][] dp = new int[n + 10][m + 10]; 76 | for(int i = 0; i <= n; i++) dp[i][0] = i; 77 | for(int j = 0; j <= m; j++) dp[0][j] = j; 78 | for(int i = 1; i <= n; i++){ 79 | for(int j = 1; j <= m; j++){ 80 | dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + 1; 81 | if(a.charAt(i - 1) == b.charAt(j - 1)) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1]); 82 | else dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1); 83 | } 84 | } 85 | return dp[n][m]; 86 | } 87 | } 88 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing901滑雪.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 901. 滑雪 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定一个R行C列的矩阵,表示一个矩形网格滑雪场。 10 | 11 | 矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。 12 | 13 | 一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。 14 | 15 | 当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。 16 | 17 | ```r 18 | 下面给出一个矩阵作为例子: 19 | 20 | 1 2 3 4 5 21 | 22 | 16 17 18 19 6 23 | 24 | 15 24 25 20 7 25 | 26 | 14 23 22 21 8 27 | 28 | 13 12 11 10 9 29 | ``` 30 | 31 | 在给定矩阵中,一条可行的滑行轨迹为24-17-2-1。 32 | 33 | 在给定矩阵中,最长的滑行轨迹为25-24-23-…-3-2-1,沿途共经过25个区域。 34 | 35 | 现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。 36 | 37 | **输入格式** 38 | 39 | 第一行包含两个整数R和C。 40 | 41 | 接下来R行,每行包含C个整数,表示完整的二维矩阵。 42 | 43 | **输出格式** 44 | 45 | 输出一个整数,表示可完成的最长滑雪长度。 46 | 47 | **数据范围** 48 | 49 | 1≤R,C≤300, 50 | 0≤矩阵中整数≤10000 51 | 52 | ```r 53 | 输入样例: 54 | 55 | 5 5 56 | 1 2 3 4 5 57 | 16 17 18 19 6 58 | 15 24 25 20 7 59 | 14 23 22 21 8 60 | 13 12 11 10 9 61 | 62 | 输出样例: 63 | 64 | 25 65 | ``` 66 | 67 | ### Solution 68 | 69 | ```java 70 | import java.util.*; 71 | import java.io.*; 72 | 73 | class Main{ 74 | static int N = 310; 75 | static int[][] g = new int[N][N]; 76 | static int[][] dp = new int[N][N]; 77 | // 四个方向 78 | static int[] dx = {-1, 0, 1, 0}, dy = {0, -1, 0, 1}; 79 | static int R, C; 80 | public static int dfs(int x, int y){ 81 | // 剪枝:判断这个点是否已经计算过 82 | if(dp[x][y] != -1) return dp[x][y]; 83 | // 一个点走不通了,最少有 1 ; 84 | dp[x][y] = 1; 85 | // 朝四个方向递归 86 | for(int i = 0; i < 4; i++){ 87 | int a = x + dx[i]; 88 | int b = y + dy[i]; 89 | // 判断 (a, b) 是否合法, 在滑雪场内 且 a[x][y] > g[a][b] 90 | if(a >= 1 && a <= R && b >= 1 && b <= C && g[x][y] > g[a][b]) 91 | dp[x][y] = Math.max(dp[x][y], dfs(a, b) + 1); 92 | } 93 | return dp[x][y]; 94 | } 95 | public static void main(String[] args) throws IOException{ 96 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 97 | String[] s = br.readLine().split(" "); 98 | R = Integer.parseInt(s[0]); 99 | C = Integer.parseInt(s[1]); 100 | for(int i = 1; i <= R; i++){ 101 | s = br.readLine().split(" "); 102 | for(int j = 1; j <= C; j++){ 103 | g[i][j] = Integer.parseInt(s[j - 1]); 104 | } 105 | } 106 | // 初始化DP数组为 -1 107 | for(int i = 0; i < N; i++) Arrays.fill(dp[i], -1); 108 | // 状态表示: dp[i][j] 表示从 (i,j) 出发的点滑雪路径的最大和 109 | // 状态计算: 递归,判断 (i,j) 是否可以继续走下去 110 | // dp[i][j] = Math.max(dp[x][y], dfs(x, y) + 1); 111 | // 初始化: dp[i][j] = -1;如果能够走到那个点 dp[x][y] = 1; 112 | int res = 0; 113 | for(int i = 1; i <= R; i++) 114 | for(int j = 1; j <= C; j++) 115 | res = Math.max(res, dfs(i, j)); 116 | System.out.println(res); 117 | } 118 | } 119 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing902最短编辑距离.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 动态规划 2 | 3 | ## AcWing 902. 最短编辑距离 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定两个字符串A和B,现在要将A经过若干操作变为B,可进行的操作有: 10 | 11 | 1. 删除–将字符串A中的某个字符删除。 12 | 2. 插入–在字符串A的某个位置插入某个字符。 13 | 3. 替换–将字符串A中的某个字符替换为另一个字符。 14 | 15 | 现在请你求出,将A变为B至少需要进行多少次操作。 16 | 17 | **输入格式** 18 | 19 | 第一行包含整数n,表示字符串A的长度。 20 | 21 | 第二行包含一个长度为n的字符串A。 22 | 23 | 第三行包含整数m,表示字符串B的长度。 24 | 25 | 第四行包含一个长度为m的字符串B。 26 | 27 | 字符串中均只包含大写字母。 28 | 29 | **输出格式** 30 | 31 | 输出一个整数,表示最少操作次数。 32 | 33 | **数据范围** 34 | 35 | 1≤n,m≤1000 36 | 37 | ```r 38 | 输入样例: 39 | 40 | 10 41 | AGTCTGACGC 42 | 11 43 | AGTAAGTAGGC 44 | 45 | 输出样例: 46 | 47 | 4 48 | ``` 49 | 50 | ### Solution 51 | 52 | ```java 53 | import java.util.*; 54 | 55 | class Main{ 56 | public static void main(String[] args){ 57 | Scanner sc = new Scanner(System.in); 58 | int n = sc.nextInt(); 59 | String a = sc.next(); 60 | int m = sc.nextInt(); 61 | String b = sc.next(); 62 | // 状态表示:dp[i][j] 表示 a 字符串的前 i 个字符与 b 字符串的前 j 个字符的最短编辑距离 63 | // 状态计算:就针对 a 字符串进行增,删,改 64 | // 增:dp[i][j] = dp[i][j - 1] + 1; 65 | // 删:dp[i][j] = dp[i - 1][j] + 1; 66 | // 改:if(a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] 67 | // if(a[i] != b[j]) dp[i][j] = dp[i - 1][j - 1] + 1; 68 | // 初始化:dp[0][j] = j, dp[i][0] = i; 69 | int[][] dp = new int[n + 10][m + 10]; 70 | for(int i = 0; i <= n; i++) dp[i][0] = i; 71 | for(int j = 0; j <= m; j++) dp[0][j] = j; 72 | for(int i = 1; i <= n; i++){ 73 | for(int j = 1; j <= m; j++){ 74 | dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + 1; 75 | if(a.charAt(i - 1) == b.charAt(j - 1)) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1]); 76 | else dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1); 77 | } 78 | } 79 | System.out.println(dp[n][m]); 80 | } 81 | } 82 | ``` -------------------------------------------------------------------------------- /5动态规划/AcWing9分组背包问题.md: -------------------------------------------------------------------------------- 1 | # LeetCode 专题 -- 背包专题 2 | 3 | ## AcWing 9. 分组背包问题 4 | 5 | `难度:中等` 6 | 7 | ### 题目描述 8 | 9 | 有 N 组物品和一个容量是 V 的背包。 10 | 11 | 每组物品有若干个,同一组内的物品最多只能选一个。 12 | 13 | 每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。 14 | 15 | 求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。 16 | 17 | 输出最大价值。 18 | 19 | **输入格式**: 20 | 21 | 第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。 22 | 23 | 接下来有 N 组数据: 24 | 25 | - 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量; 26 | - 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值; 27 | 28 | **输出格式**: 29 | 30 | 输出一个整数,表示最大价值。 31 | 32 | **数据范围**: 33 | 34 | - 0 < N,V ≤ 100 35 | - 0 < Si ≤ 100 36 | - 0 < vij,wij ≤ 100 37 | 38 | **示例 1**: 39 | 40 | ```r 41 | 3 5 42 | 2 43 | 1 2 44 | 2 4 45 | 1 46 | 3 4 47 | 1 48 | 4 5 49 | 50 | 输出样例: 51 | 52 | 8 53 | ``` 54 | 55 | 56 | **链接**: 57 | 58 | > 59 | 60 | ### Solution 61 | 62 | 63 | **思路**: 64 | 65 | 背包问题,循环的顺序**物品-->体积-->决策**。 66 | 67 | **进阶版**: 68 | 69 | ```java 70 | import java.util.*; 71 | 72 | class Main{ 73 | public static void main(String[] args){ 74 | Scanner sc = new Scanner(System.in); 75 | int N = sc.nextInt(); 76 | int V = sc.nextInt(); 77 | int[] dp = new int[V + 10]; 78 | // 循环顺序为 物品种类 -> 体积 -> 决策 79 | // 还是 01 背包的思想,只不过 01 背包是放或者不放 80 | // 分组背包就是多一层循环,放或者不放第 i 个,一组里只能放一个 81 | for(int i = 1; i <= N; i++){ 82 | int s = sc.nextInt(); 83 | int[] v = new int[s]; 84 | int[] w = new int[s]; 85 | for(int k = 0; k < s; k++){ 86 | v[k] = sc.nextInt(); 87 | w[k] = sc.nextInt(); 88 | } 89 | for(int j = V; j >= 0; j--){ 90 | for(int k = 0; k < s; k++){ 91 | if(j >= v[k]) dp[j] = Math.max(dp[j], dp[j - v[k]] + w[k]); 92 | } 93 | } 94 | } 95 | sc.close(); 96 | System.out.println(dp[V]); 97 | } 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /6贪心/AcWing905区间选点.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 贪心专题 2 | 3 | ## AcWing 905. 区间选点 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。 10 | 11 | 输出选择的点的最小数量。 12 | 13 | 位于区间端点上的点也算作区间内。 14 | 15 | **输入格式** 16 | 17 | 第一行包含整数N,表示区间数。 18 | 19 | 接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。 20 | 21 | **输出格式** 22 | 23 | 输出一个整数,表示所需的点的最小数量。 24 | 25 | **数据范围** 26 | 27 | $1≤N≤10^5,$ 28 | $−10^9≤ai≤bi≤10^9$ 29 | 30 | ```r 31 | 输入样例: 32 | 33 | 3 34 | -1 1 35 | 2 4 36 | 3 5 37 | 38 | 输出样例: 39 | 40 | 2 41 | ``` 42 | 43 | ### Solution 44 | 45 | ```java 46 | import java.util.*; 47 | 48 | class Main{ 49 | static class Pair{ 50 | int a, b; 51 | public Pair(int a, int b){ 52 | this.a = a; 53 | this.b = b; 54 | } 55 | } 56 | static int N = 100010; 57 | static int INF = 0x3f3f3f3f; 58 | static Pair[] g = new Pair[N]; 59 | public static void main(String[] args){ 60 | Scanner sc = new Scanner(System.in); 61 | int n = sc.nextInt(); 62 | for(int i = 1; i <= n; i++){ 63 | int a = sc.nextInt(); 64 | int b = sc.nextInt(); 65 | g[i] = new Pair(a, b); 66 | } 67 | Arrays.sort(g, 1, n + 1, (Pair p1, Pair p2) -> p1.b - p2.b); 68 | int res = 0; 69 | int right = -INF; 70 | for(int i = 1; i <= n; i++){ 71 | // 如果这个区间的左端点大于上一次的右边界,就加一个点,更新右边界 72 | if(g[i].a > right){ 73 | res ++; 74 | right = g[i].b; 75 | } 76 | } 77 | System.out.println(res); 78 | } 79 | } 80 | ``` -------------------------------------------------------------------------------- /6贪心/AcWing908最大不相交区间数量.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 -- 贪心专题 2 | 3 | ## AcWing 908. 最大不相交区间数量 4 | 5 | `难度:简单` 6 | 7 | ### 题目描述 8 | 9 | 给定N个闭区间[ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。 10 | 11 | 输出可选取区间的最大数量。 12 | 13 | **输入格式** 14 | 15 | 第一行包含整数N,表示区间数。 16 | 17 | 接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。 18 | 19 | **输出格式** 20 | 21 | 输出一个整数,表示可选取区间的最大数量。 22 | 23 | **数据范围** 24 | 25 | $1≤N≤10^5,$ 26 | $−10^9≤ai≤bi≤10^9$ 27 | 28 | ```r 29 | 输入样例: 30 | 31 | 3 32 | -1 1 33 | 2 4 34 | 3 5 35 | 36 | 输出样例: 37 | 38 | 2 39 | ``` 40 | 41 | ### Solution 42 | 43 | ```java 44 | import java.util.*; 45 | 46 | class Main{ 47 | static class Pair{ 48 | int a, b; 49 | public Pair(int a, int b){ 50 | this.a = a; 51 | this.b = b; 52 | } 53 | } 54 | static int N = 100010; 55 | static int INF = 0x3f3f3f3f; 56 | static Pair[] g = new Pair[N]; 57 | public static void main(String[] args){ 58 | Scanner sc = new Scanner(System.in); 59 | int n = sc.nextInt(); 60 | for(int i = 1; i <= n; i++){ 61 | int a = sc.nextInt(); 62 | int b = sc.nextInt(); 63 | g[i] = new Pair(a, b); 64 | } 65 | Arrays.sort(g, 1, n + 1, (Pair p1, Pair p2) -> p1.b - p2.b); 66 | int res = 0; 67 | int right = -INF; 68 | for(int i = 1; i <= n; i++){ 69 | // 如果这个区间的左端点大于上一次的右边界,就加一个区间,更新右边界 70 | if(g[i].a > right){ 71 | res ++; 72 | right = g[i].b; 73 | } 74 | } 75 | System.out.println(res); 76 | } 77 | } 78 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AcWing 算法基础课 2 | 3 | AcWing 算法基础课 六讲 笔记 4 | 5 | ## 第一讲 基础算法 6 | 7 | 包括排序、二分、高精度、前缀和与差分、双指针算法、位运算、离散化、区间合并等内容。 8 | 9 | ## 第二讲 数据结构 10 | 11 | 包括单链表,双链表,栈,队列,单调栈,单调队列,KMP,Trie,并查集,堆,哈希表等内容。 12 | 13 | ## 第三讲 搜索与图论 14 | 15 | 包括DFS,BFS,树与图的深度优先遍历,树与图的广度优先遍历,拓扑排序,Dijkstra,bellman-ford,spfa,Floyd,Prim,Kruskal,染色法判定二分图,匈牙利算法等内容。 16 | 17 | ## 第四讲 数学知识 18 | 19 | 包括质数,约数,欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理,高斯消元,求组合数,容斥原理,博弈论等内容。 20 | 21 | ## 第五讲 动态规划 22 | 23 | 包括背包问题,线性DP,区间DP,计数类DP,数位统计DP,状态压缩DP,树形DP,记忆化搜索等内容。 24 | 25 | ## 第六讲 贪心 26 | 27 | 包括区间问题,Huffman树,排序不等式,绝对值不等式,推公式等内容。 28 | 29 | ## 题目模板来源 30 | 31 | 链接:https://www.acwing.com/activity/content/introduction/11/ 32 | 来源:AcWing --------------------------------------------------------------------------------