前言
注:1.P8649 K倍区间答案参考于“晏秋”,P8742 砝码称重答案参考于“一只风里”,感谢两位大佬提供的解法!
2.如有不正确的地方,欢迎指正,谢谢!
前缀和
P8772 求和
import java.util.Scanner;
public class Mian {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();//输入整数的个数
int[] A = new int[n];//用一个数组接收这些整数
int pre = 0;
long[] sum = new long[n];
//输入整数
for(int i = 0;i < n;i++) {
int num = scan.nextInt();
A[i] = num;
pre += num;
sum[i] += pre;//求出每个整数对应的前缀和
}
//用前缀和数组进行优化
//a1*a2 + a1*a3 + ... + a1*an
//a1*(a2 + a3 + ... +an) = a1*(sum(n) - sum(1))
long total = 0;//用于接收最终的结果——所求的和
for(int i = 0;i < n;i++) {
total += A[i] * (sum[n - 1] - sum[i]);
}
System.out.println(total);
}
}
P8649 K倍区间
答案参考:晏秋
import java.util.Scanner;
public class main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int K = scan.nextInt();
int[] A = new int[N];
long sum = 0;
long[] B = new long[K];//存放各个余数的个数(这里不太理解为什么要用long)
for(int i = 0;i < N;i++) {
A[i] = scan.nextInt();
sum += A[i];//把前缀和求出来
B[(int)(sum % K)]++;
}
long count = 0;//接收结果
count = B[0];//余数为0的个数(余数为0则必定为K的倍数)
for(int i = 0;i < K;i++) {
//两个同余的数相减一定为K倍区间
//用排列组合公式Cn,即从具有相同余数的前缀和里取出两个前缀和进行相减
count += (B[i] * (B[i] - 1)) / 2;
}
System.out.println(count);
}
}
并查集
基本操作
并查集主要用于不相交集合的合并问题
1.初始化,即将每个节点的祖先节点指向自己
2.查询祖先节点find
3.合并两个节点:将一个节点的祖先节点指向另一个节点的祖先节点
P8654 合根植物
import java.util.Scanner;
public class Main {
static int[] fa;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
int m = scan.nextInt();
int n = scan.nextInt();
int k = scan.nextInt();
//用二维数组存放连根数据
int[][] arr = new int[k][2];
for(int i = 0;i < k;i++) {
int a = scan.nextInt();
int b = scan.nextInt();
arr[i][0] = a;
arr[i][1] = b;
}
//初始化:将每个植物节点的祖先节点指向自己
//为了方便,数组0位置不使用
fa = new int[m * n + 1];
for(int i = 1;i < fa.length;i++) {
fa[i] = i;
}
int count = m*n;
for(int i = 0;i < k;i++) {
int x_fa = find(arr[i][0]);
int y_fa = find(arr[i][1]);
//当x与y的祖先节点相同时,无需将x的祖先节点指向y的祖先节点,count无需减1
if(x_fa == y_fa) {
continue;
}
else {
//让x的祖先节点指向y的祖先节点
fa[x_fa] = y_fa;
count--;
}
}
System.out.println(count);
}
public static int find(int node) {
if(fa[node] == node) {
return node;
}
else {
//比如1→5→6,1的父节点为5,则find(5),即继续寻找5的父节点
//直到找到最终节点6,6的父节点指向自己,则进入if语句,return 6;所以fa[1] = 6;
fa[node] = find(fa[node]);//该步进行了路径压缩
return fa[node];
}
}
}
二分查找
P8647 分巧克力
import java.util.Scanner;
public class Main {
static int N;
static int K;
static int[] H;
static int[] W;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
N = scan.nextInt();
K = scan.nextInt();
H = new int[N];//存放各块巧克力的长
W = new int[N];//存放各块巧克力的宽
for(int i = 0;i < N;i++) {
int h = scan.nextInt();
int w = scan.nextInt();
H[i] = h;
W[i] = w;
}
System.out.println(find(1,100000));
}
//二分查找
public static int find(int l,int r) {
int ans = -1;
int mid = 0;
while(l <= r) {
mid = (l + r) / 2;
if(account(mid)) {
//当以mid为边长分出巧克力块数大于孩子数时
//继续向右边寻找更大的边长
ans = mid;
l = mid + 1;
}
else {
//当以mid为边长分出巧克力块数小于孩子数时
//向左寻找(将边长缩小一点,快数会多一点)
r = mid - 1;
}
}
return ans;
}
public static boolean account(int a) {
int count = 0;
for(int i = 0;i < N;i++) {
count += (H[i] / a)*(W[i] / a);
}
if(count >= K) {
return true;
}
else {
return false;
}
}
}
01背包问题
P8742 砝码称重
答案参考:一只风里
import java.util.Scanner;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int[] fama = new int[N];//用于存放各个砝码的重量
for(int i = 0;i < N;i++) {
fama[i] = scan.nextInt();
}
HashSet<Long> set = new HashSet<Long>();
set.add(0L);
for(int i = 0;i < N;i++) {
//不能直接使用set的迭代器遍历,迭代器不能及时发现set的更新
//(自己的理解,如不对请指正,谢谢!)
ArrayList<Long> list = new ArrayList<Long>(set);
for(long k:list) {
set.add(fama[i] + k);
set.add(Math.abs(k - fama[i]));
}
}
set.remove(0L);
System.out.println(set.size());
}
}
深度优先搜索(DFS)
P8605 网络寻路
import java.util.Scanner;
public class Main {
static int sum;//统计路径
static int[] node;//用于标记节点是否已访问
static int[][] map;//用于标记两个节点之间是否有线路
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int M = scan.nextInt();
//为了方便,数组0位置不使用,从1开始
node = new int[N + 1];
map = new int[N + 1][N + 1];
for(int i = 0;i < M;i++) {
int u = scan.nextInt();
int v = scan.nextInt();
map[u][v] = 1;//1表示节点之间有线路
map[v][u] = 1;
}
for(int i = 1;i < N + 1;i++) {
node[i] = 0;//node数组初始化,0表示未访问,1表示已访问
}
for(int i = 1;i < N + 1;i++) {
dfs(i,i,0);
}
System.out.println(sum);
}
//传入三个参数,start表示起始节点,temp表示当前节点,step表示步长(即连接节点的线路条数)
//1.判断是否为终点,非终点时继续递归调用dfs
//2.递归调用dfs前需要将当前节点设为已访问,为了防止走回头路;
// 调用结束后需要将当前节点设为未访问,为了探索其它路径时可以访问该节点
//此题注意:当步长为2时,需要判断当前节点与起始节点是否有线路,如果有,则sum++
public static void dfs(int start,int temp,int step) {
if(step == 3) {
sum++;
return;
}
else if(step == 2) {
if(map[temp][start] == 1) {
sum++;
}
for(int k = 1;k < map[temp].length;k++) {
if(map[temp][k] == 1 && node[k] == 0) {
node[temp] = 1;
dfs(start,k,step + 1);
node[temp] = 0;
}
}
}
else {
for(int k = 1;k < map[temp].length;k++) {
//如果temp和k之间有线路,且k未访问
if(map[temp][k] == 1 && node[k] == 0) {
node[temp] = 1;
//当前节点变为k,步长+1
dfs(start,k,step + 1);
node[temp] = 0;
}
}
}
}
}
有两个测试点未通过,显示超出空间限制,请问大家有什么更好的解吗?