参考
【算法1-3】暴力枚举 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
- P2241 统计方形(数据加强版)
- P2089 烤鸡
- P1618 三连击(升级版)
- P1036 [NOIP2002 普及组] 选数
- P1157 组合的输出
- P1706 全排列问题
- P1088 [NOIP2004 普及组] 火星人
- P3392 涂国旗
- P3654 First Step (ファーストステップ)
- P1217 [USACO1.5] 回文质数 Prime Palindromes
- P1149 [NOIP2008 提高组] 火柴棒等式
- P3799 妖梦拼木棒
- P2392 kkksc03考前临时抱佛脚
- P2036 [COCI2008-2009 #2] PERKET
循环枚举
P2241 统计方形(数据加强版)
只能过40%?
对于一个n*m的格子,如果在n边放边长为1的小方格,能放n个,以此类推t边放i长的小方格能放(t-i+1)
个,那么对于i*j的矩形,在n*m中总共可以放n边能放的个数*m边能放的个数。
其中i=j时便是正方形,反之为长方形,对i和j做循环,对总数做累加即可。
package _1_3.xun_huan_mei_ju;
import java.util.Scanner;
public class P2241 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
// 行/列中边长为i(1、2……t)的可能位置个数=t、t-1……1,即(t-i+1)
// lenN记录在n边中,边长为i的位置个数
// lenM记录在m边中,边长为i的位置个数
int[] lenN = new int[n + 1];
int[] lenM = new int[m + 1];
for (int i = 1; i <= n; i++) {
lenN[i] = n - i + 1;
}
for (int i = 1; i <= m; i++) {
lenM[i] = m - i + 1;
}
// 对于正方形,sum1等于 宽1*长1 + 宽2*长2 此类的累加
// 对于长方形,sum2等于 宽1*长其他值 + 宽2*长其他值 此类的累加
// ij分别表示两个边长
int sum1 = 0;
int sum2 = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i != j) {
sum2 += lenN[i] * lenM[j];
} else {
sum1 += lenN[i] * lenM[j];
}
}
}
System.out.printf("%d %d", sum1, sum2);
scanner.close();
}
}
子集枚举
P1036 [NOIP2002 普及组] 选数
dfs搜索,结束条件是size==k。
去重:不降原则,记录index从而使得循环从index后面开始
package _1_3.zi_ji_mei_ju;
import java.util.Arrays;
import java.util.Scanner;
public class P1036 {
private static int k;
private static int n;
private static int[] nums;
private static int count;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String[] s1 = scanner.nextLine().split(" ");
int[] ints = Arrays.stream(s1).mapToInt(Integer::valueOf).toArray();
n = ints[0];
k = ints[1];
count = 0;
String[] s2 = scanner.nextLine().split(" ");
nums = Arrays.stream(s2).mapToInt(Integer::valueOf).toArray();
// 注意size从0开始,加上k个数后size=k-1,再search(k)进行判断
search(0, 0, 0);
System.out.println(count);
scanner.close();
}
public static void search(int size, int sum, int index) {
if (size == k) {
if (isPrim(sum)) count++;
return;
}
for (int i = index; i < n; i++) {
sum += nums[i];
search(size + 1, sum, i + 1);
sum -= nums[i];
}
}
public static boolean isPrim(int n) {
if (n <= 1) {
return false;
}
for (int i = 2; i < n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
}
排列枚举
P1088 [NOIP2004 普及组] 火星人
方法一:20%,暴力枚举,全排列找到start,然后再找m个就是结果
改进:在找到start这个部分剪枝,参考 洛谷刷题之P1088 [NOIP2004普及组] 火星人 - 知乎 (zhihu.com)
package _1_3.pai_lie_mei_ju;
import java.util.Scanner;
public class P1088 {
private static String start;
private static int n;
private static int add;
private static boolean flag;
private static int count;
private static boolean toContinue;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = Integer.parseInt(scanner.nextLine());
add = Integer.parseInt(scanner.nextLine());
start = scanner.nextLine().replaceAll(" ", "");
toContinue = true;
flag = false;
// count必须为全局,否则会跟随dfs变化
count = 0;
dfs(new StringBuilder());
scanner.close();
}
// 一直全排列到符合start,然后再进行add次全排列即可得到最终结果
public static void dfs(StringBuilder now) {
if (!toContinue) {
return;
}
if (count == add) {
for (char c : now.toString().toCharArray()) {
System.out.printf("%c ", c);
}
toContinue = false;
return;
}
for (int i = 1; i <= n; i++) {
if (now.indexOf(i + "") == -1) {
now.append(i);
// 一直搜索到start,此期间count不变
if (!flag) {
// algo List<String>->String
// 1 String.join("-",list)
// 2 list.stream().collect(Collectors.joining()).toString();
// 3 StringBuilder
if (now.toString().equals(start)) {
flag = true;
}
dfs(now);
} else {
// 已经从start开始搜索
if (now.length() == n) {
// 找到start之后一个符合长度的顺序
count++;
dfs(now);
} else dfs(now);
}
now.deleteCharAt(now.length() - 1);
}
}
}
}
方法二:字段序算法获取当前序列的下一个序列,参考
当前序列的下一个序列(字典序算法)Java实现_java中如何插入序列的下一个值-CSDN博客
package _1_3.pai_lie_mei_ju;
import java.util.Scanner;
public class P1088_2 {
private static int[] start;
private static int n;
private static int k;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = Integer.parseInt(scanner.nextLine());
k = Integer.parseInt(scanner.nextLine());
start = new int[n];
for (int i = 0; i < n; i++) {
start[i] = scanner.nextInt();
}
for (int i = 0; i < k; i++) {
next_permutation(start);
}
for (int i = 0; i < n; i++) {
System.out.printf("%d ", start[i]);
}
scanner.close();
}
public static void next_permutation(int[] arr) {
// algo 字典序算法
// 设P是集合{1、2、……n-1、n}的一个全排列:P = p1p2……pj-1pjpj+1……pn(1 ≤ p1、 p2、……、pn ≤ n-1)
// 第一步:从排列的右端开始,找出第一个比右边数字小的数字的序号j,即j = max { i | pi < pi+1 ,i > j }
// 第二步:在pj 的右边的数字中,找出所有比pj大的数字中最小的数字pk,即 k = min { i | pi > pj ,i > j}
// 第三步:交换pi,pk
// 第四步:再将排列右端的递减部分 [pj+1pj+2……pn]倒转,就得到了一个新的排列 P’ = p1p2…pj-1pkpnpn-1…pk+1pjpk-1…pj+1
for (int i = n - 1; i > 0; i--) {
// 1、从后往前找第一对逆序,逆序对前一个下表为t
if (arr[i - 1] < arr[i]) {
int t = i - 1;
// 如果t==n-1,只需要交换最后两个数即可
if (t == n - 2) {
swap(arr, t, n - 1);
} else {
// 2、在t之后的数中,找到比t大且符合条件的最小的数
// 注意这里的min是下标,先找到符合比t大的作为min的初始值,否则各种错误(如果设置t+1为初始值,不能保证arr[t+1]比arr[t]大)
int min = t;
while (arr[min] <= arr[t]) min++;
for (int j = t + 1; j < n; j++) {
if (arr[j] > arr[t] && arr[j] < arr[min]) {
min = j;
}
}
// 3、交换t和min的值
swap(arr, t, min);
// 4、在t之后的数中,进行正序排序
// 可以注意到,t之后的数都是符合arr[i - 1] >= arr[i]
// 交换t和min之后形成了一个递减的数列,所以对递减数列进行翻转即可得到正序
int left = t + 1, right = n - 1;
while (left < right) {
swap(arr, left, right);
left++;
right--;
}
}
return;
}
}
}
private static void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
left++;
right--;
}
}
return;
}
}
}
private static void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}