一、牛的学术圈Ⅰ(二分)
如果不考虑参数L的影响,那就是一个纯纯的二分答案,问题在于如何处理参数L?,仔细读题,题目说了总共至多引用L篇,每篇论文至多引用1次,至多的意思是说可以引用也可以不引用。 那我们就先不考虑L的影响,遍历所有论文,只要论文的引用次数比二分答案(指数),大或等于或者比答案小1都可以算满足,然后统计出来看总的>=h指数的论文篇数是否达到了h篇(>=),是不是很简单?这个时候再考虑L的影响,L无非是对论文引用+1的次数有限制,那我们只要在统计论文篇数时取L和之前和答案差1的论文篇数的较小值即可。
import java.util.*;
public class Main {
static int[] cnt;
static int N, L;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
N = scan.nextInt();
L = scan.nextInt();
// N 论文篇数
// L 能够自己引用的论文数(可以提高某个论文的引用数)至多每篇论文引用一次
cnt = new int[N];
for (int i = 0; i < N; i++) cnt[i] = scan.nextInt();
int left = 0;
// 最大h指数只能为N + 1(因为每个论文至多再+1)
int right = N + 1;
int mid = 0;
int ans = 0;
while (left <= right) {
mid = left + (right - left) / 2;
if (check(mid)) {
// 当前指数可以,那就往大的找
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
System.out.println(ans);
}
static boolean check(int mid) {
int tmp = 0;
// 下面记录需要添加引用才能够的论文
int tmpp = 0;
for (int i = 0; i < N; i++) {
if (cnt[i] >= mid) tmp++;
else if (mid - cnt[i] == 1) tmpp++;
}
// 最多引用L次
tmpp = Math.min(tmpp, L);
tmp += tmpp;
// 当前满足mid指数的文章数>=mid指数就可以
if (tmp >= mid) return true;
return false;
}
}
二、打包(二分)
二分答案即可,关键是二分里面的check内容不太好理解:
import java.util.*;
public class Main {
static int n, m;
static int[] gift;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
gift = new int[n];
int left = 0;
int right = 0;
for (int i = 0; i < n; i++) {
gift[i] = scan.nextInt();
left = Math.max(left, gift[i]);
right += gift[i];
}
// 二分区间:[max(gift), sum(gift)]
int mid = 0;
int ans = 0;
while (left <= right) {
mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid;
// 最大重量最小,往小的缩小
right = mid - 1;
} else {
left = mid + 1;
}
}
System.out.println(ans);
}
static boolean check(int mid) {
// 当前最大重量是否满足m个包裹的需求?
int curPackage = 0; // 当前包裹数
int curWeight = 0; // 当前包裹总量
for (int i = 0; i < n; i++) {
// 注意要连续打包
curWeight += gift[i];
if (curWeight > mid) {
// 大于最大重量的要求,重新打一个包裹
curPackage++;
curWeight = gift[i];
}
}
// 注意,只要包裹数大于题目要求包裹数就肯定false
if (curPackage + 1 > m) return false;
// 但如果包裹书小于等于题目要求就说明当前mid所代表的最大重量太大了,还可以缩减
return true;
}
}
记需要包的数量为sum。如果sum<=m,也就是说最大重量Target拿完所有礼物绰绰有余,例如本来需要4个包才能装下的现在只需要两个,Target的值可能太大了,那么答案ans<=Target;如果sum>m,说明最大重量Target下装不完所有礼物,那只能够增大Target的值,答案ans>Target,枚举直到二分查找结束。
※三、闯关(动态规划、最大子序列和问题)
这道题非常有意思,它限制了必须从第一关开始,最后一关结束,然后想要到达最后一关时,取得的金钱数最大,并且限制了每次能够到达的位置关系,抛开其它所有限制条件,回到问题本身,这就是一道:求数组最大子序列和的问题(子序列指保证数组顺序的相对关系,可以删除数组中某一个、几个元素,也可以不删除,但不能改变数组的顺序)
dp[i]表示以第 i 关结束的最多能够获取的金币数,我们可以初始化所有的dp[i] = 第一关能够获取到的金币数量,因为第一关必须打,然后对于后面每一关,我们遍历可以从前面跳转到该位置的关卡(可以从第一关、第二关…当然要满足题目对m的限制),直到遍历到最后一关,我们需要的答案就是dp[n]。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
int p = scan.nextInt();
long[] v = new long[n + 1];
for (int i = 1; i <= n; i++) {
v[i] = scan.nextLong();
// 提前计算好每一关能够赚的金币数
v[i] -= p;
}
// 必须从第1关打起
long[] dp =new long[n + 1];
// 每个位置必须从第1关开始
Arrays.fill(dp, v[1]);
// dp[i]: 以第i关结尾的最多金钱数
for (int i = 2; i <= n; i++) {
// 必须从第一关开始打起,所以第一关就不用考虑
int begin = Math.max(1, i - m);
// 遍历前面能够到达关卡i的关卡j
long max = Long.MIN_VALUE;
for (int j = begin; j <= i - 1; j++) {
max = Math.max(max, dp[j] + v[i]);
}
dp[i] = max;
}
System.out.println(dp[n]);
}
}
这道题真的很巧妙,用的模板就是很老套、很熟悉的模板,但是如果不细细品味,很难发掘这道题的思路。
※四、怪物森林(二分答案+BFS)
题目要求是找到所有路径中,每条路径中的最小值的最大值,就是先找每条路径的最小值,然后保证这个最小值在所有路径中最大。尝试过很多方法,包括优先队列啥的,发现没用,后面看了大佬的方法,太牛了!二分答案,然后再用BFS去判断,看当前的攻击力是否能够达到终点即可。
这道题用C++可以跑过,但是Java由于本身System.out.println读入读出很慢,所以后面几个点超时了,思路是正确的。
import java.util.*;
class node {
int x, y;
node(){}
node (int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
static int[] x = new int[] {0,0,1,-1};
static int[] y = new int[] {1,-1,0,0};
static int[][] monster;
static boolean[][] vis;
static Queue<node> queue;
static int n, m;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
monster = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
monster[i][j] = scan.nextInt();
}
}
int left = -1000000000;
int right = monster[0][0];
int mid = 0;
int ans = 0;
// 二分答案
while (left <= right) {
mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
System.out.println(ans);
}
static boolean check(int mid) {
// BFS广搜
queue = new LinkedList<>();
vis = new boolean[n][m];
queue.offer(new node(0, 0));
vis[0][0] = true;
while (!queue.isEmpty()) {
node tmp = queue.poll();
for (int i = 0; i < 4; i++) {
int tx = tmp.x + x[i];
int ty = tmp.y + y[i];
if (tx < 0 || ty < 0 || tx >= n || ty >= m || vis[tx][ty]) continue;
// 二分的是答案,也就是路径上最小的攻击力,不能有攻击力比mid还小,否则肯定不能走这里
if (monster[tx][ty] < mid) continue;
vis[tx][ty] = true;
queue.offer(new node(tx, ty));
}
}
// 判断当前攻击力是否能够访问到终点
return vis[n - 1][m - 1];
}
}
※五、智能体系列赛(DFS搜搜)
刚开始想的是最小生成树,发现不对劲啊,这东西要求必须从起点开始,最小生成树只是说连通所有节点就行,很明显不对,发现问题后赶紧转变思路,因为每个点都能到任意点,所以每次dfs搜索需要遍历所有点,当然要用vis数组标记。
import java.beans.Visibility;
import java.util.*;
public class Main {
static int[] x = new int[] {0,0,1,-1};
static int[] y = new int[] {1,-1,0,0};
static int ans = Integer.MAX_VALUE;
static int n;
static boolean[] vis;
static int[][] mine;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int xi = scan.nextInt();
int yi = scan.nextInt();
n = scan.nextInt(); // 矿点数
// 读取每个矿点坐标,把边存一下
mine = new int[n][2];
vis = new boolean[n];
for (int i = 0; i < n; i++) {
mine[i][0] = scan.nextInt();
mine[i][1] = scan.nextInt();
}
// 注意呀vis数组中压根没存开始位置,所以开始位置只会被遍历1次
dfs(xi, yi, 0, 0);
System.out.println(ans);
}
static void dfs(int x, int y, int cnt, int step) {
if (step > ans) return;
if (cnt == n) {
// 所有位置遍历完了
ans = Math.min(ans, step);
return;
}
// 遍历可能去到的点的位置
for (int i = 0; i < n; i++) {
if (vis[i]) continue;
step += Math.abs(x - mine[i][0]) + Math.abs(y - mine[i][1]);
cnt++; // 去过的地方++
vis[i] = true;
dfs(mine[i][0], mine[i][1], cnt, step);
// 回溯
vis[i] = false;
cnt--;
step -= Math.abs(x - mine[i][0]) + Math.abs(y - mine[i][1]);
}
}
}
※六、秘密行动(动态规划)
在每一层它可以向上跳1、2层(直接是跳1、2层楼),也可以靠花费1个单位时间走1高度(每个楼层有各自的高度),现在说:跳了之后,必须靠走,才能继续跳,问到达顶层需要至少多少时间?
考虑当前层 i,它可以由上一层跳、或者走上来,所以这就有两种状态,一个是楼层数,一个是路径方法,所以是个二维DP。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] hei = new int[n + 1];
for (int i = 1; i <= n; i++) hei[i] = scan.nextInt();
int[][] dp = new int[n + 1][2];
dp[1][0] = 0; // 第一层楼靠跳
dp[1][1] = hei[1]; // 第一层楼靠走
for (int i = 2; i <= n; i++) {
// 到达这一层靠走,那可以跳完走,也可以走了继续走,反正只可能从上一层转换过来
dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][1]) + hei[i]; // 走的话必须要把这一层楼走完
// 到达这一层靠跳,那前面必须是走,跳的话可以跳1、2层
dp[i][0] = Math.min(dp[i - 1][1], dp[i - 2][1]);
}
System.out.println(Math.min(dp[n][1], dp[n][0]));
}
}
七、搬运冰块(贪心)
注意本题,冰块在搬运过程中是不融化的!看样例输入,我们按照冰块ti / di的比值,从小到大排序即可。
import java.util.*;
class ice {
int t, d;
ice(){}
ice(int t, int d) {
this.t = t;
this.d = d;
}
}
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
ice[] ices = new ice[n];
for (int i = 0; i < n; i++) {
int tt = scan.nextInt();
int dd = scan.nextInt();
ices[i] = new ice(tt, dd);
}
Arrays.sort(ices, new Comparator<ice>() {
@Override
public int compare(ice o1, ice o2) {
int t1 = o1.t;
int d1 = o1.d;
int t2 = o2.t;
int d2 = o2.d;
// t/d 两个分数比较大小
t1 *= d2;
t2 *= d1;
int tmp = d1;
d1 *= d2;
d2 *= tmp;
return t1 - t2;
}
});
long cnt = 0;
for (int i = 0; i < n; i++) {
int t = ices[i].t;
for (int j = i + 1; j < n; j++) {
cnt += (long)(t * ices[j].d);
}
}
System.out.println(cnt);
}
}
八、Cat And Mouse(模拟搜索)
让猫和老鼠一起动,遇到障碍物就按题目意思进行转向,这里必须要猫鼠一起动,单靠猫、鼠一个人动,是不能找到递归出口的!导致一直递归下去,StackOverflow。
import java.util.*;
public class Main {
static char[][] map;
public static void main(String[] args) {
/**
*...*.....
......*...
...*...*..
..........
...*.C....
*.....*...
...*......
..M......*
...*.*....
.*.*......
*/
Scanner scan = new Scanner(System.in);
map = new char[10][10];
// 记录到达每个点的时间
// 读入地图,并记录猫、老鼠的起点
int cati = 0, catj = 0;
int mousei = 0, mousej = 0;
for (int i = 0; i < 10; i++) {
map[i] = scan.nextLine().toCharArray();
for (int j = 0; j < 10; j++) {
if (map[i][j] == 'C') {
cati = i;
catj = j;
}
if (map[i][j] == 'M') {
mousei = i;
mousej = j;
}
}
}
// 分两次遍历猫、老鼠行进路线即可
// 平时沿直线走,下一步如果会走到障碍物上去或者出界,就用1秒的时间做一个右转90°。一开始它们都面向北方。
dfs(cati, catj, mousei, mousej, 0, 0, 0);
}
// 猫的坐标、老鼠的坐标
static void dfs(int i, int j, int k, int t, int dirC, int dirM, int time) {
if (i == k && j == t) {
// 相遇了
System.out.println(time);
return;
}
int ti = i, tj = j, tk = k, tt = t;
if (dirC == 0) {
if (i - 1 < 0 || map[i - 1][j] == '*') {
// 转向不移动
dirC = 3;
} else {
// 可以走
ti -= 1;
}
} else if (dirC == 1) {
// 左走
if (j - 1 < 0 || map[i][j - 1] == '*') {
// 转向不移动
dirC = 0;
} else {
// 可以走
tj -= 1;
}
} else if (dirC == 2) {
// 下走
if (i + 1 >= 10 || map[i + 1][j] == '*') {
// 转向不移动
dirC = 1;
} else {
// 可以走
ti += 1;
}
} else {
// 右走
if (j + 1 >= 10 || map[i][j + 1] == '*') {
// 转向不移动
dirC = 2;
} else {
// 可以走
tj += 1;
}
}
// 老鼠的位置 i j k t
if (dirM == 0) {
if (k - 1 < 0 || map[k - 1][t] == '*') {
// 转向不移动
dirM = 3;
} else {
// 可以走
tk -= 1;
}
} else if (dirM == 1) {
// 左走
if (t - 1 < 0 || map[k][t - 1] == '*') {
// 转向不移动
dirM = 0;
} else {
// 可以走
tt -= 1;
}
} else if (dirM == 2) {
// 下走
if (k + 1 >= 10 || map[k + 1][t] == '*') {
// 转向不移动
dirM = 1;
} else {
// 可以走
tk += 1;
}
} else {
// 右走
if (t + 1 >= 10 || map[k][t + 1] == '*') {
// 转向不移动
dirM = 2;
} else {
// 可以走
tt += 1;
}
}
dfs(ti, tj, tk, tt, dirC, dirM, time + 1);
}
}
九、答疑(贪心)
按照题目意思,因为后面的同学会一直等待前面在问问题的同学,也就是说前面的同学问问题的时间越久,后面同学等的越久,累计总和越大,所以,我们可以按照三个同学的三个时间累加和,从小到大排序,这样就可以保证后面同学的等待时间较短。
import java.util.*;
class stu {
int s, a, e;
stu(){}
stu(int s, int a, int e) {
this.s = s;
this.a = a;
this.e = e;
}
}
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
stu[] S = new stu[n];
for (int i = 0; i < n; i++) {
S[i] = new stu();
S[i].s = scan.nextInt();
S[i].a = scan.nextInt();
S[i].e = scan.nextInt();
}
// 按照时间总和从小到大排序,因为后面发消息的时刻一定会累加前面发消息的时刻,包括其它的等待时间
// 所以让总和从小到大排序,保证每个人的发消息时间都小
Arrays.sort(S, new Comparator<stu>() {
@Override
public int compare(stu o1, stu o2) {
return (o1.s + o1.a + o1.e) - (o2.s + o2.a + o2.e);
}
});
// 计算发消息的时刻总和
long ans = 0;
long curTime = 0;
for (int i = 0; i < n; i++) {
curTime = curTime + S[i].a + S[i].s;
ans += curTime;
curTime += S[i].e;
}
System.out.println(ans);
}
}
十、矩阵翻转
这道题试了DFS,但是超时了,还没看懂网上大佬咋做的…