文章目录
题目链接
搜索专题-牛客链接
比赛密码:henau202301082000
知识点
题目列表
快输
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
out.flush();
}
static class FastReader{
BufferedReader br;
StringTokenizer st;
String tmp;
public FastReader() {
br=new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while(st==null||!st.hasMoreElements()) {
try {
st=new StringTokenizer(br.readLine());
}catch(IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
long nextLong(){return Long.parseLong(next());}
String nextLine() {
String str="";
try {
str=br.readLine();
}catch(IOException e) {
e.printStackTrace();
}
return str;
}
boolean hasNext(){
if(st!=null&&st.hasMoreTokens())return true;
try {
tmp=br.readLine();
st=new StringTokenizer(tmp);
}catch(IOException e) {
return false;
}
return true;
}
}
static PrintWriter out=new PrintWriter(
new BufferedWriter(new OutputStreamWriter(System.out)));
static FastReader sc=new FastReader();
}
A - Knight Moves(BFS/双向BFS)
题意是找骑士从一个点行进到另外一个点的最少步数,可以用广搜直接找最优解,比深搜用时更少。另外还需要创建一个骑士类,来保存骑士在行进过程中的状态。
BFS解法
//骑士类
class Node{
int x,y,step;//当前位置 (x,y) & 行进的步数
Node(int x,int y,int step){
this.x=x;
this.y=y;
this.step=step;
}
}
static int n,l,sx,sy,ex,ey;//骑士数量,棋盘大小,起始位置,结束位置
static int dx[]=new int[]{-1,-1,1,1,-2,-2,2,2};
static int dy[]=new int[]{2,-2,2,-2,1,-1,1,-1};
static int vis[][];//该位置是否已访问
static Queue<Node> q;//存储一个骑士在找最优解过程的所有状态
public static void main(String[] args) {
n=sc.nextInt();
while(n-->0) {
l=sc.nextInt();
sx=sc.nextInt();sy=sc.nextInt();
ex=sc.nextInt();ey=sc.nextInt();
vis=new int[l][l];
//每个骑士都需要new一个新队列来保存状态
//也可以用数组来模拟队列,两个指针分别指向首尾
q=new ArrayDeque<>();
bfs(sx,sy,ex,ey);
out.flush();
}
}
//深搜易超时,用广搜找最优解
static void bfs(int sx,int sy,int ex,int ey) {
vis[sx][sy]=1;
q.add(new Node(sx,sy,0));
Node no;
int x,y,step;//中间变量
while(!q.isEmpty()) {
no=q.poll();
x=no.x;
y=no.y;
step=no.step;
//找到则为最优解(此时step值最小)
if(x==ex&&y==ey) {
out.println(step);
break;
}
//骑士可从该位置向8个方向行进,分别将对应状态存入队列中
for(int i=0;i<8;i++) {
int nx=x+dx[i];
int ny=y+dy[i];
//对于超出临界位置或者已经遍历过的位置(重复遍历意味着不可能达到最小值)跳过
if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==1) continue;
//该位置遍历过,需要vis数组的状态
vis[nx][ny]=1;
q.add(new Node(nx,ny,step+1));
}
}
}
BFS-数组模拟队列
static node q[]=new node[100005];//存储所有状态下的骑士
static void bfs(int sx,int sy,int ex,int ey) {
int head=1,tail=1;
vis[sx][sy]=1;
q[tail]=new node(sx,sy,0);
tail++;
while(head<tail){
int x=q[head].x;
int y=q[head].y;
int step=q[head].step;
//找到即为最优解(step值最小)
if(x==ex&&y==ey){
out.printf("%d\n",step);out.flush();
break;
}
for(int i=0;i<8;i++){
int nx=x+dx[i];
int ny=y+dy[i];
if(nx>=0&&nx<n&&ny>=0&&ny<n&&vis[nx][ny]==0)
{
vis[nx][ny]=1;
q[tail]=new node(nx,ny,step+1);
tail++;
}
}
head++;
}
}
双向BFS
这个解法就是指,从起点和终点同时出发进行搜索,二者第一次相遇的位置对应的步数即为最优解;即原本的解答树规模是 an,使用双向搜索后,规模立刻缩小到了约 2an/2 ,当n较大时优化非常可观。
浅测了一下,对于本题并没有多少优化,但是也能过。
最优解为第一次分别从起点或终点到达碰撞位置所用步数之和,需要加一个数组dis更新从终点或起点到达该碰撞位置。
以及骑士类与上相同,不再贴。
static int n,l,sx,sy,ex,ey;//骑士数量,棋盘大小,起始位置,结束位置
static int dx[]=new int[]{-1,-1,1,1,-2,-2,2,2};
static int dy[]=new int[]{2,-2,2,-2,1,-1,1,-1};
static int vis[][];//分别标记从起点/终点出发是否已访问该位置,从起点访问标记1,否则标2
static int dis[][];//更新从对面位置出发到该碰撞点走过的距离
static Queue<Node> qs,qe;//存储一个骑士在从起点/终点出发找最优解过程的所有状态
public static void main(String[] args) {
n=sc.nextInt();
while(n-->0) {
l=sc.nextInt();
sx=sc.nextInt();sy=sc.nextInt();
ex=sc.nextInt();ey=sc.nextInt();
vis=new int[l][l];
dis=new int[l][l];
qe=new ArrayDeque<>();
qs=new ArrayDeque<>();
if(sx==ex&&sy==ey)out.println(0);
else bfs(sx,sy,ex,ey);
out.flush();
}
}
static void bfs(int sx,int sy,int ex,int ey) {
vis[sx][sy]=1;
vis[ex][ey]=2;
qs.add(new Node(sx,sy,0));
qe.add(new Node(ex,ey,0));
Node no;
int now=0;//now表示为当前遍历回合数(保证取出的是同一行进距离的骑士)
//从起点/终点同时出发,有一个队列为空则停止
c:
while(!qs.isEmpty()&&!qe.isEmpty()) {
while(qs.peek().step==now){
no=qs.poll();
for(int i=0;i<8;i++){
int nx=no.x+dx[i];
int ny=no.y+dy[i];
if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==1) continue;
//从终点出发遍历过该位置,此时找到最优解(step值最小)
if(vis[nx][ny]==2){
out.println(no.step+1+dis[nx][ny]);
break c;
}
qs.add(new Node(nx,ny,no.step+1));
vis[nx][ny]=1;
dis[nx][ny]=no.step+1;
}
}
while(qe.peek().step==now) {
no=qe.poll();
for(int i=0;i<8;i++){
int nx=no.x+dx[i];
int ny=no.y+dy[i];
if(nx<0||nx>=l||ny<0||ny>=l||vis[nx][ny]==2) continue;
//从起点出发遍历过该位置,此时找到最优解(step值最小)
if(vis[nx][ny]==2){
out.println(no.step+1+dis[nx][ny]);
break c;
}
qe.add(new Node(nx,ny,no.step+1));
vis[nx][ny]=2;
dis[nx][ny]=no.step+1;
}
}
now++;
}
}
算法对比(测试平台Vjudge)
B - 迷宫(一)
直接深搜找可行解
static int n,m;//棋盘大小
static int dx[]={-1,1,0,0};
static int dy[]={0,0,-1,1};
static int[][] mp;//存地图
static boolean f=false;//判断能否顺利走出迷宫
public static void main(String[] args) {
n=sc.nextInt();
m=sc.nextInt();
mp=new int[n][m];
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
mp[i][j]=sc.nextInt();
}
}
dfs(0,0);
out.println(f?"YES":"NO");
out.flush();
}
//深搜找可行解
static void dfs(int x,int y) {//(x,y)表示当前位置
//排除临界情况
if (x<0||x>=n||y<0||y>=m||mp[x][y]==1)return;
//搜索到目标直接返回
if (x==n-1&&y==m-1) {
f=true;
return;
}
mp[x][y]=1;//地图内可直接进行标记,经过该点标记为1
for(int i=0;i<4;i++){
int nx=x+dx[i];
int ny=y+dy[i];
dfs(nx,ny);
}
}
C - 迷宫(二)(BFS)
跟A题类似,除了多了一个关于钥匙开门的条件,只需要注意遍历该位置时是否带着钥匙属于不同的状态。
static int H,W,sx,sy;//迷宫大小,起始位置
static int dx[]={-1,1,0,0};
static int dy[]={0,0,-1,1};
static char[][] mp;//存迷宫
static int vis[][][];//该位置是否已访问,第三个维度表示走过该位置是否拿着钥匙
static Queue<Node> q;//存储找最优解过程的所有状态
public static void main(String[] args) {
H=sc.nextInt();
W=sc.nextInt();
mp=new char[H][W];
vis=new int[H][W][2];
q=new ArrayDeque<>();
for(int i=0;i<H;i++){
mp[i]=sc.next().toCharArray();
for(int j=0;j<W;j++) {
if(mp[i][j]=='S') {
sx=i;
sy=j;
}
}
}
out.println(bfs(sx,sy));
out.flush();
}
//广搜找最优解
static int bfs(int sx,int sy) {
vis[sx][sy][0]=1;//标记起点状态
q.add(new Node(sx,sy,0,0));
Node no;
while(!q.isEmpty()){
no=q.poll();
int x=no.x,y=no.y,key=no.key,step=no.step;
//找到终点,即找到最优解
if(mp[x][y]=='E') return step;
for(int i=0;i<4;i++) {
int nx=dx[i]+x;
int ny=dy[i]+y;
//临界状况跳过
if(nx<0||nx>=H||ny<0||ny>=W)continue;
if(vis[nx][ny][key]==1||mp[nx][ny]=='W'||(mp[nx][ny]=='D'&&key==0))continue;
vis[nx][ny][key]=1;
q.add(new Node(nx,ny,mp[nx][ny]=='K'?1:key,step+1));//拿到钥匙则钥匙数标记为1,否则钥匙数不变
}
}
//没有找到通路
return -1;
}
D - 老子的全排列呢
深搜过程回溯状态是因为,该题目的是求所有可行解,而判断状态的数组(pd)需要更新为当前状态。
static int n=8;//1-8全排列
static int[] pd=new int[n],used=new int[n];//判断某未数字是否已使用,存储当前方案的一个排列
public static void main(String[] args) throws IOException{
dfs(0);
}
static void dfs(int k) {//k为选择的第几个数字
if(k>=n) {//条件为k==n即可,已选择n个数字,则输出该排列
out.print(used[0]);
for(int i=1;i<n;i++)out.print(" "+used[i]);
out.println();
out.flush();
return;
}
else {
for(int i=0;i<n;i++) {
if(pd[i]==0) {
pd[i]=1;
used[k]=i+1;
dfs(k+1);
//遍历所有方案,回溯
pd[i]=0;
}
}
}
}
E - [NOIP2002]选数
解法一:dfs常规解法
static int n,k,ans=0;//共n个数,选k个,和为素数的种类数
static int a[];//存储数列
public static void main(String[] args) {
n=sc.nextInt();
k=sc.nextInt();
a=new int[n];
for(int i=0;i<n;i++)a[i]=sc.nextInt();
dfs(0,0,0);
out.println(ans);
out.flush();
}
//深搜求所有可行解
static void dfs(int cnt,int sum,int start){
/** @param
* cnt代表现在选择了多少个数
* sum表示当前的和
* start表示选数起始值
*/
//选够k个数
if(cnt==k){
if(isprime(sum))ans++;//和为素数,结果+1
return ;
}
//步数要加一,和也要加
//选数起始值要变成i+1,以免算重
for(int i=start;i<n;i++)
dfs(cnt+1,sum+a[i],i+1);
}
//判断是否为质数
static boolean isprime(int n){
for(int i=2;i*i<=n;i++){
if(n%i==0)return false;
}
return true;
}
解法二:
在搜索过程中直接求素数种类总数,即将深搜目的改为返回当前素数之和是否为质数,是质数返回1,相当于返回:存在一种素数和,结果+1;
static int n,k,ans=0;
static int a[];
public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
n=sc.nextInt();
k=sc.nextInt();
a=new int[n];
for(int i=0;i<n;i++)a[i]=sc.nextInt();
out.println(dfs(k,0,0));
out.flush();
}
static int dfs(int k,int sum,int start){
//k为还需要选中的个数,sum为前面累加的和
//start和end为全组合剩下数字的选取范围;调用递归生成全组合
//在过程中逐渐把K个数相加,当选取的数个数为0时,直接返回前面的累加和是否为质数即可
if(k==0)return isprime(sum)?1:0;
int cnt=0;
for(int i=start;i<n;i++){
cnt+=dfs(k-1,sum+a[i],i+1);
}
return cnt;
}
//判断是否质数
static boolean isprime(int n){
for(int i=2;i*i<=n;i++){
if(n%i==0)return false;
}
return true;
}
F - 素数圆环
输入的整数不超过19,最大相邻数字之和不超过37,可以直接用存储1-37是否为素数的数组进行判断。
static int n,cnt=0;//输入数据,case数
static boolean[] vis;//vis[i] : 标记数字i是否已访问
static int[] ans;//存答案序列
//判断素数(0~39)
static int isprime[]={0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,
0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0};
public static void main(String[] args) throws IOException{
while(sc.hasNext()) {
out.printf("Case %d:\n",++cnt);
n=sc.nextInt();
//下标从1开始
vis=new boolean[n+1];
ans=new int[n+1];
ans[1]=1;
dfs(2);//从第2个数开始,第一个数为1
out.println();//case之间存在空行
out.flush();
}
}
//深搜求所有可行解
static void dfs(int k){//k指要选择的数字是第几个数
//选够n个数,进行输出
if(k>n) {
//判断首尾元素之和是否为素数
if(isprime[1+ans[k-1]]==1) {
out.print(ans[1]);
for(int i=2;i<=n;i++)
out.printf(" %d",ans[i]);
out.println();
}
return;
}
//从数字2开始判断
for(int i=2;i<=n;i++) {
//如果这个数字访问过或者跟上一个写入的数字之和为非素数,则跳过
if(vis[i]||isprime[i+ans[k-1]]==0)continue;
vis[i]=true;
ans[k]=i;
dfs(k+1);
//遍历所有方案,回溯
vis[i]=false;
}
}
G - Lake Counting(DFS找连通块)
题目意思就是找出有多少个连通区域。按照题目要求,如果某点为’W’(池塘),则其八个方向上同为池塘的点都算与之相连。对于搜索过的点,直接将其在数组中转为’.'(旱地),之后就不会重复搜索。
static int n,m,ans=0;//农田大小,水田总数
static char mp[][];//存储图
public static void main(String[] args) throws IOException{
n=sc.nextInt();
m=sc.nextInt();
mp=new char[n][m];
for(int i=0;i<n;i++){
mp[i]=(sc.next()).toCharArray();
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
//每一次搜索将联通的一片区域转为旱地
if(mp[i][j]=='W'){
ans++;//有水方格,结果就+1
dfs(i,j);//传入该点坐标进行搜索
}
}
}
out.println(ans);out.flush();
}
public static void dfs(int x,int y){
int nx,ny;
//总共搜索9个点,包括本身这个点
//为保证不重复,搜过就将该点在mp数组中标记为'.'
for(int i=-1;i<=1;i++){
for(int j=-1;j<=1;j++){
nx=x+i;//横坐标
ny=y+j;//纵坐标
if(nx>=0&&nx<n&&ny>=0&&ny<m&&mp[nx][ny]=='W'){
mp[nx][ny]='.';//如果该点是'W'的话,就令该点是 '.' 以后也不访问它
dfs(nx,ny);// 去搜索该点
}
}
}
}
H - 水图(DFS/Dijkstra)
思路:由题目可知具有n个点和n-1条边的无向联通图是一棵树(如下图所示)。
当树具有不同的分支,则要想求从某个点出发经过该图中的每个点至少一次的最小距离,一个思路是找一条从起点出发到所有点中的一条最长路径,用所有边的权重之和(sum)的两倍减去这条路径的长度(maxLen),得到就是每个点至少经过一次所需要的最小距离(ans),即:
ans = sum*2 - maxLen
如下例中,sum=1+2+3=6,maxLen=1+3=4,则ans=6*2-4=8,即路径1->2->3->2->4;
解法一:
DFS解法,Vector存邻接表,深搜求最长路径(maxLen)
//节点类
class Node{
int v,w;//该边的另一个点,边的权重
Node(int v,int w){
this.v=v;
this.w=w;
}
}
static int n,x;//点数,小w所处的位置(起始点)
//存邻接表
static Vector<Node>[] g;
public static void main(String[] args) {
n=sc.nextInt();
x=sc.nextInt();
long ans=0;
g=new Vector[n+1];
//点的标记从1开始
for(int i=1;i<=n;i++) g[i]=new Vector<>();
for(int i=1;i<n;i++) {
int u=sc.nextInt(),v=sc.nextInt(),w=sc.nextInt();
g[u].add(new Node(v,w));
g[v].add(new Node(u,w));
ans+=2*w;
}
out.println(ans-dfs(x,0));
out.flush();
}
//深搜找从起始点出发,距离最长的一条路径(不一定经过所有点)
static long dfs(int x,int fa){
//注意每一次搜索都要重置ans,这样返回给最终结果时才不会求重复
long ans=0;
for(Node no:g[x]){
if(no.v!=fa){
ans=Math.max(ans,dfs(no.v,x)+no.w);
}
}
return ans;
}
解法二:Dijkstra
Dijkstra算法目的是为了找到起点到其它所有顶点之间的最短距离,具体实现就是利用贪心思想,每次从未遍历的点中找距离顶点最近的点加入已遍历顶点集合,其中会维护一个数组(d)存储所有点到顶点的最短距离。
本题中也可以利用Dijkstra算法求最长路径,只需要在遍历找最短路径时,将路径实际长度以相反数(正数中较小值,反而变成负数中较大值)的方式存入距离类来进行遍历,而d数组存储的依然是实际的路径长度。
//节点类
class Node{
int v,w;//该边的另一个点,边的权重
Node(int v,int w){
this.v=v;
this.w=w;
}
}
//距离类
class Dis{
int dis,idx;//距离,顶点下标
Dis(int dis,int idx){
this.dis=dis;
this.idx=idx;
}
}
static int maxn=(int)1e5;
static int n,x;//点数,小w所处的位置(起始点)
//存邻接表
static Vector<Node>[] g;
static int[] d=new int[maxn];
public static void main(String[] args) {
n=sc.nextInt();
x=sc.nextInt();
long sum=0;
d=new int[n+1];
//起初均标记为1e9,逐步更新距离,方便找最小值
Arrays.fill(d,(int)1e9);
d[x]=0;//起点到起点距离为0
g=new Vector[n+1];
//点的标记从1开始
for(int i=1;i<=n;i++) g[i]=new Vector<>();
for(int i=1;i<n;i++) {
int u=sc.nextInt(),v=sc.nextInt(),w=sc.nextInt();
g[u].add(new Node(v,w));
g[v].add(new Node(u,w));
sum+=w;
}
Dijkstra();
int mx=0;
//找最大路径距离
for(int i=1;i<=n;i++)
mx=Math.max(mx,d[i]);
out.println(sum*2-mx);
out.flush();
}
static void Dijkstra(){
Queue<Dis> q=new ArrayDeque<>();
q.add(new Dis(-d[x],x));
while(!q.isEmpty()) {
int now=q.poll().idx;
for(Node no:g[now]){
int v=no.v;
/** 说明:
* ①某点到起点的距离最小,则与之相连的点到起点的距离也应最小,
* 利用上面的思想,不断更新数组d的值
* ②数组d中的值依然是点到起点的最大距离:
* 因为遍历过程中是以-d[v]进行标记寻找
*/
if(d[v]>d[now]+no.w){
d[v]=d[now]+no.w;
q.add(new Dis(-d[v],v));
}
}
}
}
I - Jelly(BFS)
BFS求最优解问题。维护一个dist数组存储行进步数,也是吃到的果冻数,而当第一次访问终点时求得的值即为最优值。
//坐标类
class Node{
int x,y,z;
Node(int x,int y,int z){
this.x=x;
this.y=y;
this.z=z;
}
}
static int dist[][][],n;
static int dx[]={-1,0,1,0,0,0},dy[]={0,1,0,-1,0,0},dz[]={0,0,0,0,1,-1};
static char ch[][][];
static Queue<Node> q=new ArrayDeque<>();
public static void main(String[] args) {
n=sc.nextInt();
dist=new int[n+1][n+1][n+1];
ch=new char[n+1][n+1][n+1];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ch[i][j]=(" "+sc.next()).toCharArray();
out.println(bfs());;
out.flush();
}
static int bfs() {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
Arrays.fill(dist[i][j],-1);
}
}
q.add(new Node(1,1,1));
dist[1][1][1]=1;
Node no;
while(!q.isEmpty()){
no=q.poll();
for(int i=0;i<6;i++){
int x=no.x+dx[i],y=no.y+dy[i],z=no.z+dz[i];
//临界情况1:超出范围
if(x<=0||x>n||y<=0||y>n||z<=0||z>n)continue;
//临界情况2:遇到障碍或者遍历过该点
//后者可以保证所求结果为第一次访问求得的值,根据BFS性质可知为最优值
if(ch[x][y][z]=='*'||dist[x][y][z]!=-1)continue;
dist[x][y][z]=dist[no.x][no.y][no.z]+1;
q.add(new Node(x,y,z));
}
}
return dist[n][n][n];
}
J - N皇后问题(DFS/位运算)
回溯法:
如果当前位置放置了一个皇后,则用三个数组分别标记对应列、对角线、反对角线的状态,标记不能在相应位置放置皇后;为得到所有可行解,需回溯这三个数组的状态。
static int n,sum=0;//棋盘大小
//存储空间要设置为棋盘大小的两倍,防止超出数组存储范围
static int a[]=new int[25],b[]=new int[25],
c[]=new int[25],d[]=new int[25];//行,列,对角线,反对角线
public static void main(String[] args) {
// TODO Auto-generated method stub
n=sc.nextInt();
dfs(1);//得到所有结果
out.println(sum);
out.flush();
}
public static void dfs(int k){//第k行
//选够n个位置
if(k>n){
sum++;return;
}
else{
for(int i=1;i<=n;i++){
if(b[i]==0&&c[k+i]==0&&d[k+n-i]==0){
a[k]=i;//标记第k行取第i个数
b[i]=1;c[k+i]=1;d[k+n-i]=1;
dfs(k+1);
//回溯状态
b[i]=0;c[k+i]=0;d[k+n-i]=0;
}
}
}
}
位运算解法:
解析在这,是力扣上的一个解法,思路太强了,解析也很详尽,蒟蒻还需要继续学习加深理解(╥ω╥`)
static int size,sum=0;
public static void main(String[] args) {
// TODO Auto-generated method stub
int n=sc.nextInt();//棋盘大小
out.println(totalNQueens(n));
out.flush();
}
static int totalNQueens(int n) {
sum=0;
size=(1<<n)-1;
solve(0,0,0);
return sum;
}
static void solve(int row, int na, int pie) {
if (row==size) {
sum++;
return;
}
int pos=size&(~(row|na|pie));
while (pos!=0) {
int p=pos&(-pos);
pos-=p;
solve(row|p,(na|p)<<1,(pie|p)>>1);
}
}
K - [NOIP2017]棋盘(BFS+优先队列优化/DFS+记忆化优化)
BFS+优先队列优化:
对本题进行分析,得到从当前位置行进方向的所有种可能(用数组dx,dy存储)以及对应方向所需要付出的魔法代价;再结合下一个格子的颜色是否不同,判断是否需要付出额外代价1;最后一个格子关于有无颜色颜色再分出两种情况。
因为行进的方向不同,也就可能具有不同的代价,所以为使BFS找到的依然是最优解,利用优先队列存储行进到每个格子需要的代价,这样在更新某个位置的代价时,就能保证为最优值。
下面代码的参考解析在这。
//可排序的节点类
class Node implements Comparable<Node>{
int x,y,c,w;//(x,y)为当前位置,c为颜色
Node(int x,int y,int c,int w){
this.x=x;
this.y=y;
this.c=c;
this.w=w;
}
Node(){}
@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
return this.w-o.w;//升序
}
}
static int inf=0x3f3f3f3f;
static PriorityQueue<Node> q=new PriorityQueue<>();
static int dx[]={0,1,0,-1,1,1,-1,-1,0,2,0,-2};//12个行进方向
static int dy[]={1,0,-1,0,1,-1,1,-1,2,0,-2,0};
static int dw[]={0,0,0,0,2,2,2,2,2,2,2,2};//相应魔法代价
static int[][] a,dis;//a存储棋盘上格子的颜色,dis存储代价
static int n,m;
public static void main(String[] args) {
int x,y,c;//坐标点及各自颜色,有颜色的格子标记为1或2
m=sc.nextInt();//棋盘的大小
n=sc.nextInt();//盘上有颜色的格子的数量
a=new int[m+1][m+1];
dis=new int[m+1][m+1];
for(int i=1;i<=n;i++){
x=sc.nextInt();
y=sc.nextInt();
c=sc.nextInt();
a[x][y]=c+1;
}//这里c+1,为了方便区分无色格子
bfs();
if(a[m][m]==0){//处理(m,m)没有颜色的情况
int ans=Math.min(dis[m][m-1],dis[m-1][m])+2;
if(ans>=inf)out.println(-1);
else out.println(ans);
}
else{//处理(m,m),即终点格子有颜色的情况
if(dis[m][m]==inf)out.println(-1);
else out.println(dis[m][m]);
}
out.flush();
}
static void bfs(){
for(int i=0;i<=m;i++)Arrays.fill(dis[i],inf);
dis[1][1]=0;
//Node(x,y,c,w)
q.add(new Node(1,1,a[1][1],dis[1][1]));
Node cur;
while(!q.isEmpty()){
cur=q.poll();
int x=cur.x,y=cur.y,c=cur.c,w=cur.w;
if(dis[x][y]<w)continue;
for(int i=0;i<12;i++){
int nx=x+dx[i];
int ny=y+dy[i];
int nw=w+dw[i];
if(nx<=0||nx>m||ny<=0||ny>m)continue;//保证在棋盘范围内
int nc=a[nx][ny];
if(nc==0)continue;
if(c!=nc)nw++;//确定下一步的信息
if(dis[nx][ny]>nw){
dis[nx][ny]=nw;
q.add(new Node(nx,ny,nc,nw));
}
}
}
}
DFS+记忆化优化(即 SPFA ):
直接搜索会超时,所以要加上记忆化剪枝优化:即在递归过程中用两个变量存储代价和魔法使用情况。
static int m,n;
static int inf=0x3f3f3f3f;
static int[][] a,dis,dir={{0,1},{1,0},{0,-1},{-1,0}};
public static void main(String[] args) {
m=sc.nextInt();
n=sc.nextInt();
a=new int[m+1][m+1];//color
dis=new int[m+1][m+1];
int x,y,c;
for(int i=1;i<=n;i++) {
x=sc.nextInt();
y=sc.nextInt();
c=sc.nextInt();
a[x][y]=c+1;
}
for(int i=0;i<=m;i++)Arrays.fill(dis[i],inf);
dfs(1,1,0,false);
if(dis[m][m]!=inf)out.println(dis[m][m]);//能走到棋盘右下角
else out.println(-1);
out.flush();
}
//用sum记忆行进到每个状态时所需要的代价,use标记上一格是否使用了魔法
//dis数组存更新后的最小代价
static void dfs(int x,int y,int sum,boolean use){
if(sum>=dis[x][y]) return;
//更新最小代价
dis[x][y]=sum;
for(int i=0;i<4;i++){
int nx=x+dir[i][0],ny=y+dir[i][1];
if(nx<=0||nx>m||ny<=0||ny>m) continue;
if(a[nx][ny]==0){//如果格子无色
if(!use) {//判断有无使用过魔法
a[nx][ny]=a[x][y];
dfs(nx,ny,sum+2,true);//魔法代价+2
a[nx][ny]=0;
}
continue;
}
if(a[nx][ny]==a[x][y])
dfs(nx,ny,sum,false);
else
dfs(nx,ny,sum+1,false);//不同颜色,额外消耗代价1
}
}
L - 数字金字塔(DFS/DP)
DP+DFS:
相当于从金字塔底部不断向上累加最大值,金字塔顶端的值(ans[1][1])即为所求。搜索的目的就在于找到这样一条使顶端和最大的路径。
static int n;
static long[][] ans;
static boolean[][] vis;
public static void main(String[] args) {
n=sc.nextInt();
ans=new long[n+1][n+1];
vis=new boolean[n+1][n+1];
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
ans[i][j]=sc.nextLong();
out.println(dfs(1,1));
out.flush();
}
static long dfs(int i,int j){
if(vis[i][j]||i==n)return ans[i][j];
ans[i][j]=ans[i][j]+Math.max(dfs(i+1,j),dfs(i+1,j+1));
vis[i][j]=true;
return ans[i][j];
}
动归:
从上往下迭代,对于当前位置来说,其上一个位置只能为(i-1,j-1)或(i-1,j);而迭代到最后一行,这一行的值不会再随着遍历本行而更新,其对应的值含义即指以当前位置(最后一行)为结尾的路径的最大和,因此判断出其中的最大值即为所求。
static int n;
static long[][] ans;
static boolean[][] vis;
public static void main(String[] args) {
n=sc.nextInt();
ans=new long[n+1][n+1];
vis=new boolean[n+1][n+1];
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
ans[i][j]=sc.nextLong();
long max=0;
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++){
if(j==i)ans[i][j]+=ans[i-1][j-1];
else if(j>1) ans[i][j]+=Math.max(ans[i-1][j-1],ans[i-1][j]);
else ans[i][j]+=ans[i-1][j];
if(i==n&&max<ans[i][j])max=ans[i][j];
}
out.println(max);
out.flush();
}