最大连通
链接:
https://www.lanqiao.cn/problems/2410/learning/
问题描述
小蓝有一个 30 行 60 列的数字矩阵,矩阵中的每个数都是 0 或 1 。
110010000011111110101001001001101010111011011011101001111110010000000001010001101100000010010110001111100010101100011110 001011101000100011111111111010000010010101010111001000010100 101100001101011101101011011001000110111111010000000110110000 010101100100010000111000100111100110001110111101010011001011 010011011010011110111101111001001001010111110001101000100011 101001011000110100001101011000000110110110100100110111101011 101111000000101000111001100010110000100110001001000101011001 001110111010001011110000001111100001010101001110011010101110 001010101000110001011111001010111111100110000011011111101010 011111100011001110100101001011110011000101011000100111001011 011010001101011110011011111010111110010100101000110111010110 001110000111100100101110001011101010001100010111110111011011 111100001000001100010110101100111001001111100100110000001101 001110010000000111011110000011000010101000111000000110101101 100100011101011111001101001010011111110010111101000010000111 110010100110101100001101111101010011000110101100000110001010 110101101100001110000100010001001010100010110100100001000011 100100000100001101010101001101000101101000000101111110001010 101101011010101000111110110000110100000010011111111100110010 101111000100000100011000010001011111001010010001010110001010 001010001110101010000100010011101001010101101101010111100101 001111110000101100010111111100000100101010000001011101100001 101011110010000010010110000100001010011111100011011000110010 011110010100011101100101111101000001011100001011010001110011 000101000101000010010010110111000010101111001101100110011100 100011100110011111000110011001111100001110110111001001000111 111011000110001000110111011001011110010010010110101000011111 011110011110110110011011001011010000100100101010110000010011 010011110011100101010101111010001001001111101111101110011101
如果从一个标为 1 的位置可以通过上下左右走到另一个标为 1 的位置,则称两个位置连通。与某一个标为 1 的位置连通的所有位置(包括自己)组成一个连通分块。
请问矩阵中最大的连通分块有多大?
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
思路
这题就是很明显的dfs。
这道题的题意就是,让我们找一个全为1的连通分块,因为会有很多种连通分块,我们只需要找到最长的,即含1最多的连通分块就可以,然后把1的个数输出即可。
就是我觉得这种需要你上下左右去遍厉二维数组的dfs题,一般都是以下思路(一般迷宫类的好像都是这么个思路):
首先需要创建一个boolean[][]b数组,用来标记(i,j)这个点是否被访问过。也可以用int型的数组代替,0-未访问过,1-访问过。然后再创建存方向的二维数组f[][],一般用{{0,1},{0,-1},{1,0},{-1,0}}来表示下、上、右、左四个方向。然后还需要一个存题中数据的一个二维数组g[][]。然后还需要一个变量ans来存最终的结果。大体都是需要这些东西的,根据题意灵活变通即可。
然后下面来说一下这一题的思路:
首先它dfs的思路是什么,就是我们先找一个切入点,这题比较特殊,这个切入点的值需要是'1',才有可能找到一个连通分块,如果切入点就是'0',那根本往下走不了了啊,刚开始就不满足条件了。所以在进行dfs之前,我们先遍厉一下二维数组g[][],如果g[i][j]=='1'&&b[i][j]==false,就说明点(i,j)就可以作为切入点。然后我们就可以从这个(i,j)点开始dfs。
那么dfs该怎么写?
通常来说dfs在这种矩阵题里面一般都是传两个参数,即x,y坐标,dfs(int x, int y)。然后dfs里大多数都会写这么一个判断条件,就是判断是否越界,如果越界了,我们直接return即可。如果没越界,我们就让b[x][y]为true,即我现在从(x,y)开始dfs,那么(x,y)这个点相当于我访问过了。然后我们就利用方向数组f[][],去在(x,y)这个点上去枚举上、下、左、右四种方向,然后每枚举一个方向,我们就用两个临时变量tx,ty去存储执行完该方向的坐标,同时还要再次判断更新后的坐标是否越界,是否被访问过,是否为'1',只有不越界、没被访问过、为''1,才能继续从这个点(tx,ty)进行进一步的dfs。
由于我通常把ans设成static,所以dfs执行完之后,ans也就更新好了,在main里面直接输出即可。
最大连通-代码
import java.util.Scanner;
public class Main {static boolean [][] b = new boolean[30][60]; // 默认全为falsestatic char [][]g = new char[30][60];static int [][]f = {{0,-1},{0,1},{-1,0},{1,0}}; // 上 下 左 右static int cnt = 0;public static void main(String[] args) {String []s = {"110010000011111110101001001001101010111011011011101001111110","010000000001010001101100000010010110001111100010101100011110","001011101000100011111111111010000010010101010111001000010100","101100001101011101101011011001000110111111010000000110110000","010101100100010000111000100111100110001110111101010011001011","010011011010011110111101111001001001010111110001101000100011","101001011000110100001101011000000110110110100100110111101011","101111000000101000111001100010110000100110001001000101011001","001110111010001011110000001111100001010101001110011010101110","001010101000110001011111001010111111100110000011011111101010","011111100011001110100101001011110011000101011000100111001011","011010001101011110011011111010111110010100101000110111010110","001110000111100100101110001011101010001100010111110111011011","111100001000001100010110101100111001001111100100110000001101","001110010000000111011110000011000010101000111000000110101101","100100011101011111001101001010011111110010111101000010000111","110010100110101100001101111101010011000110101100000110001010","110101101100001110000100010001001010100010110100100001000011","100100000100001101010101001101000101101000000101111110001010","101101011010101000111110110000110100000010011111111100110010","101111000100000100011000010001011111001010010001010110001010","001010001110101010000100010011101001010101101101010111100101","001111110000101100010111111100000100101010000001011101100001","101011110010000010010110000100001010011111100011011000110010","011110010100011101100101111101000001011100001011010001110011","000101000101000010010010110111000010101111001101100110011100","100011100110011111000110011001111100001110110111001001000111","111011000110001000110111011001011110010010010110101000011111","011110011110110110011011001011010000100100101010110000010011","010011110011100101010101111010001001001111101111101110011101"};for(int i = 0;i < 30;i ++) {for(int j = 0;j < 60;j ++) {g[i][j] = s[i].charAt(j);}}// 找一个可以切入的点int ans = 0;for(int i = 0;i < 30;i ++) {for(int j = 0;j < 60;j ++) {cnt = 0;if (g[i][j] == '1' && !b[i][j]) {dfs (i,j);}ans = Math.max(ans,cnt);}}System.out.println(ans); // ans = 148}public static void dfs (int x, int y) {cnt ++;if (x < 0 || x >= 30 || y < 0 || y >= 60 || g[x][y] == '0' || b[x][y]) {return; // 不满足dfs条件}// 这就可以开始正式的dfsb[x][y] = true;// 遍厉四个方向,看看有没有可通的路for(int i = 0;i < 4;i ++) {int tx = x + f[i][0];int ty = y + f[i][1];if (tx >= 0 && tx < 30 && ty >= 0 && ty < 60 && g[tx][ty] == '1' && !b[tx][ty]) {dfs (tx , ty);}}}
}
五子棋对弈
链接:https://www.lanqiao.cn/problems/19694/learning/=
问题描述
"在五子棋的对弈中,友谊的小船说翻就翻?" 不!对小蓝和小桥来说,五子棋不仅是棋盘上的较量,更是心与心之间的沟通。这两位挚友秉承着"友谊第一,比赛第二"的宗旨,决定在一块 5×5 的棋盘上,用黑白两色的棋子来决出胜负。但他们又都不忍心让对方失落,于是决定用一场和棋(平局)作为彼此友谊的见证。
比赛遵循以下规则:
- 棋盘规模:比赛在一个 5×5 的方格棋盘上进行,共有 25 个格子供下棋使用。
- 棋子类型:两种棋子,黑棋与白棋,代表双方。小蓝持白棋,小桥持黑棋。
- 先手规则:白棋(小蓝)具有先手优势,即在棋盘空白时率先落子(下棋)。
- 轮流落子:玩家们交替在棋盘上放置各自的棋子,每次仅放置一枚。
- 胜利条件:率先在横线、竖线或斜线上形成连续的五个同色棋子的一方获胜。
- 平局条件:当所有 25 个棋盘格都被下满棋子,而未决出胜负时,游戏以平局告终。
在这一设定下,小蓝和小桥想知道,有多少种不同的棋局情况,既确保棋盘下满又保证比赛结果为平局。
答案提交
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
语言 最大运行时间 最大运行内存 C++ 1s 256M C 1s 256M Java 3s 512M Python3 10s 512M PyPy3 3s 512M Go 5s 512M JavaScript 5s 512M
思路
这一题也是矩阵那种的,但是它和上一题最大连通还有点不一样。
但是我又说不清具体有什么区别。我的理解就是,最大连通那题它dfs路径我们是不可预知的,也就是说它可以随意拐弯,所以我们只有通过设置四个方向去这样遍厉,才能够找到所有可能的结果。
但是这道题不太一样,因为五子棋对弈这道题它的矩阵大小是5x5,这样五子棋胜利的情况就直接被限定死了,只有竖着5颗同色棋子、横着5颗同色棋子、对角线5颗同色棋子。也就是有这3大类的情况(横、竖、斜),所以我觉得这种类型我们也没有设置方向的必要。我们可以直接按照横、竖、斜三种方向分类就行,判断有没有人获胜就行。那么怎么判断???
所以这一类的dfs就需要一个check()函数,去判断有没有人获胜。如果check为true,就说明是平局,也有了一种答案;为false就不可以,就说明肯定有人赢了。
那么如何设计check()?
因为题目要求是平局,所以我们在dfs中直接判断赢的情况,只要赢了,就直接返回false。只有3大类情况都没有人赢,我们才能返回true,然后我们让答案ans++。
dfs里面需要check()函数,那么dfs的其他部分该怎么写呢?
因为题目中白棋先手,所以自始至终棋盘上的白棋数都会比黑棋数多一,因为总共是5x5,所以平局总共就有25个子,易得白棋最多13个,黑棋最多12个。知道黑白棋数的关系,下面我们就该想一想,怎么下棋?
下棋肯定需要棋盘和棋子的坐标,那么坐标怎么求?
棋盘我们设为g[][],g = 1为黑子,g = 0为白子。然后下面求每个棋子对应的坐标,设num为棋盘上的总棋子数,所以很容易求得,坐标x为num/5,坐标y为num%5。然后知道坐标之后就可以开始下棋,设白棋数为w,黑棋数为b,如果w < 13,我们就下白子,dfs(num+1,w+1, b);如果b < 12就下黑子,dfs(num + 1, w, b + 1)。两个dfs全部执行完之后,所有可能的棋盘方案数也就枚举完成了。当num == 25的时候,我们就用check()判断一下就行,true就ans++,false就直接return。
五子棋对弈-代码
感觉这题还是讲的不明白,还没理解到精华部分,一表述出来就会冒很多问题。。。。。。
如果看不懂可以直接看代码,代码我觉得还挺好理解的。
import java.util.Scanner;
public class Main {static int[][]g = new int[5][5];static int ans = 0;static boolean[][]b = new boolean[5][5];public static void main(String[] args) {dfs(0,0,0);System.out.println(ans); // ans = 3126376}public static void dfs(int num,int w,int b) { // num棋盘上总棋子数, w白子个数, b黑子个数if (num == 25) {if (check()) {ans ++;}return;}// 找到每个棋子对应的坐标int x = num / 5;int y = num % 5;// 开始下棋子if (w < 13) { // 白棋先手,所以最终会比黑棋多1个g[x][y] = 0; // 下白子dfs(num + 1,w + 1,b);}if (b < 12) {g[x][y] = 1; // 下黑子dfs(num + 1,w,b + 1);}}public static boolean check () { // 判断是否有人胜出,应该是没人胜出ans才加1// 检查棋盘// 先判断行、列有没有连成线的for(int i = 0;i < 5;i ++) {int rcnt = 0; // 统计行上边有没有连成线的int ccnt = 0; // 统计列上边有没有连成线的for(int j = 0;j < 5;j ++) {rcnt += g[i][j];ccnt += g[j][i];}if (rcnt == 0 || ccnt == 5) {return false; // 有人胜出}if (rcnt == 5 || ccnt == 0) {return false;}}// 判断对角线上边int d1 = 0;int d2 = 0;for(int i = 0;i < 5;i ++) {d1 += g[i][i];d2 += g[i][4-i];}if (d1 == 0 || d2 == 5) {return false;}if (d1 == 5 || d2 == 0) {return false;}return true;}
}
分糖果
链接:https://www.lanqiao.cn/problems/4124/learning/
问题描述
两种糖果分别有 9 个和 16 个,要全部分给 7 个小朋友,每个小朋友得到的糖果总数最少为 2 个最多为 5 个,问有多少种不同的分法。糖果必须全部分完。
只要有其中一个小朋友在两种方案中分到的糖果不完全相同,这两种方案就算作不同的方案。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
思路
这题我感觉是一维的dfs,甚至都没用到数组。
题意就是让你把两种糖果分给7个小朋友,每个小朋友只能得到2-5个糖果,且糖果必须全部分完,让你求可行的方案数。
我感觉这题的代码可能算不上dfs,通俗点来说应该是的完完全全的暴力。
先看那个dfs()的思路,我们传进去的参数是i,表示在给第i个小朋友分糖果。那么如果i>n(小朋友总数),就说明我们已经给7个小朋友分完糖果了,但是分完糖果就完事了吗?答案是否定的。我们还需判断两种糖果是否有剩余,只有他们全为0的时候,才说明是全分完的,我们才能让ans++。然后我们还要判断是否有越界的,就是糖果不能一直往下减,如果有一个减到0以下了,我们就直接return。最后那个for循环就是dfs的核心,j表示我们给第i个小朋友总共发了j个糖果,然后里面的k表示,第一种糖果我给了第i个小朋友k个,那么第二种糖果很显然就给了j-k个。如果运行完b-=j-k这一行代码,就说明第i个小朋友我们已经完成分配糖果的任务了,所以我们直接再给下一个小朋友i+1进行分配dfs(i+1);但是要注意我们dfs(i+1)之后还要恢复,因为有可能dfs(i+1)是不满足的,所以我们还需要从第i个状态去遍厉dfs另一条分支。
最后输出结果ans就可以了。
分糖果-代码
import java.util.Scanner;public class Main {static int a = 9;static int b = 16;static int n = 7;static int ans = 0;public static void main(String[]args) {dfs (1);System.out.println(ans);}public static void dfs (int i) {if (i > n) {if (a == 0 & b == 0) {ans ++;}return;}if (a < 0 || b < 0) {return;}for(int j = 2;j <= 5;j ++) {for(int k = 0;k <= j;k ++) {a -= k;b -= j - k;dfs (i + 1);a += k;b += j - k;}}}
}
飞机降落
链接:https://www.lanqiao.cn/problems/3511/learning/
问题描述
N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di 个单位时间,即它最早可以于 Ti 时刻开始降落,最晚可以于 Ti+Di 时刻开始降落。降落过程需要 Li 个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 N 架飞机是否可以全部安全降落。
输入格式
输入包含多组数据。
第一行包含一个整数 T ,代表测试数据的组数。
对于每组数据,第一行包含一个整数 N 。
以下 N 行,每行包含三个整数:Ti ,Di 和 Li 。
输出格式
对于每组数据,输出 YES 或者 NO ,代表是否可以全部安全降落。
样例输入
2 3 0 100 10 10 10 10 0 2 20 3 0 10 20 10 10 20 20 10 20
样例输出
YES NO
样例说明
对于第一组数据,可以安排第 3 架飞机于 0 时刻开始降落,20 时刻完成降落。安排第 2 架飞机于 20 时刻开始降落,30 时刻完成降落。安排第 1 架飞机于 30 时刻开始降落,40 时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。
评测用例规模与约定
对于 30% 的数据,N≤2N≤2。
对于 100% 的数据,1≤T≤10 ,1≤N≤10 ,0≤Ti,Di,Li≤10^5 。
运行限制
语言 最大运行时间 最大运行内存 C++ 2s 256M C 2s 256M Java 3s 256M Python3 4s 256M PyPy3 4s 256M Go 4s 256M JavaScript 4s 256M
思路
这道题其实和五子棋对弈那题有一点点相似,就是它不是传统的那种走迷宫形式的dfs,所以这种题目dfs传进去的参数就比较灵活,也需要我们好好的考虑以下如何进行dfs。
这道题我一开始做的时候也没有思路,还以为是贪心。后来看了大佬们的题解就恍然大悟,也才意识到dfs传进去的参数是多么的灵活。
首先这道题的题意就是,根据给你的N架飞机的到达机场时间、盘旋时间以及降落所需时间,去判断,存不存在一种合理的方式,能够让这N架飞机降落成功。所谓的降落成功就是题目中的:一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
所以题意还是挺好理解的,下面的重点就是,我们怎么用代码去找合理的降落顺序?
我们先想一想,我们用什么去衡量能不能让当前飞机开始降落?答案很显然,应当是总时间。就是当前飞机的到达时间 + 当前飞机的盘旋时间 >= 前面飞机降落完成的时间,即总时间为前面飞机降落完成的时间。如果满足这个条件:当前飞机的到达时间 + 当前飞机的盘旋时间 >= 前面飞机降落完成的时间,那么当前飞机是一定可以降落的,只不过当前飞机降落完成的时间会不一定而已。为什么这么说?因为如果当前飞机到达的时候,就已经大于前一个飞机降落完成的时间了,那么这架飞机一到这就可以直接降落,所以这架飞机降落完的时间就是这架飞机的到达时间+当前飞机降落所需时间;如果当前飞机到达的时候,前面的飞机正在降落,那么我们就要先盘旋一会,所以最终当前飞机降落完成的时间就是,前一架飞机降落完成的时间+这架飞机的降落时间。所以在dfs传进去的参数中,我们要传一个x,表示这是第几架飞机;还要传一个totalTime,表示总时间。最开始就从dfs(0,0)开始就可以,如果dfs里面的x能够等于N,就说明能够合理安排这N架飞机,返回true,打印YES;反之,返回false,打印NO。
飞机降落-代码
import java.util.Scanner;
public class Main {static int N,T;static boolean flag ;static int[]t; // 到达机场时间static int[]d; // 盘旋时间static int[]l; // 降落所需时间static boolean[]b;public static void main(String[] args) {Scanner sc = new Scanner(System.in);T = sc.nextInt();while(T-- > 0) {N = sc.nextInt();t = new int[N];d = new int[N];l = new int[N];b = new boolean[N];for(int i = 0;i < N;i ++) {t[i] = sc.nextInt();d[i] = sc.nextInt();l[i] = sc.nextInt();}dfs(0,0);if (flag) {System.out.println("YES");}else {System.out.println("NO");}flag = false; // 因为是多组输入,最后重置一下flag,设成默认值false}}public static void dfs (int x, int totalTime) {if (x == N) {flag = true;return;}for(int i = 0;i < N;i ++) {if (b[i]) {continue;}b[i] = true;if (t[i] + d[i] >= totalTime) { // 到达时间 + 盘旋时间 >= 上一架飞机的'完成'降落时间 就可以降落if (t[i] > totalTime) { // 到达时间 > 前一个飞机完成降落的时间dfs(x+1,t[i] + l[i]); // 判断下一架飞机 , 时间更新为飞机x+1到达时间 + 对应的降落时间。因为这种情况它是到这就能飞的,所以直接用到达时间加就可以。}else if (t[i] <= totalTime) {dfs(x+1,totalTime + l[i]); // 这种就是到达时间 <= 前一个飞机完成降落的时间,所以我们要等前一个飞机飞完,即等到totalTime就可以,然后加上飞机x+1的降落时间即可。}}b[i] = false; // 回溯取消标记}}
}