挖地雷(记忆化搜索)
Posted leaf-2234
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了挖地雷(记忆化搜索)相关的知识,希望对你有一定的参考价值。
原题:
挖地雷
时间限制: 1 Sec 内存限制: 125 MB题目描述
在一个地图上有N个地窖(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。
输入格式
有若干行。
第1行只有一个数字,表示地窖的个数N。
第2行有N个数,分别表示每个地窖中的地雷个数。
第3行至第N+1行表示地窖之间的连接情况:
第3行有n-1个数(0或1),表示第一个地窖至第2个、第3个、…、第n个地窖有否路径连接。如第33行为1 1 0 0 0 … 0,则表示第1个地窖至第2个地窖有路径,至第3个地窖有路径,至第4个地窖、第5个、…、第n个地窖没有路径。
第4行有n?2个数,表示第二个地窖至第3个、第4个、…、第n个地窖有否路径连接。
… …
第n+1行有1个数,表示第n-1个地窖至第n个地窖有否路径连接。(为0表示没有路径,为1表示有路径)。
输出格式
有两行
第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。
第二行只有一个数,表示能挖到的最多地雷数。
输入输出样例
输入
5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1
输出
1 3 4 5
27
题意:
给定一张单向图(注意单向!否则你练样例都过不了),每个顶点有一个点权,求从其中任意一个顶点出发不回头,最多能获得的权值总和。
Generally speaking,DP题我们会先考虑DFS的做法(如果你对DP不太了解,请点这里)。
让我们先把路径的处理放一边去。
先用二维数组g记录每两个地窖之间能否到达,从每一个顶点出发对所有能遍历的边都尝试一遍,记录最大值,代码如下(直接提交后果自负):
1 #include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 int n,ans,a[25],mark[25],g[25][25]; 5 void dfs(int x,int rec)//简洁易懂的dfs 6 { 7 ans=max(ans,rec); 8 for(int i=1;i<=n;i++) 9 { 10 if(g[x][i] && !mark[i]) 11 { 12 mark[i]=1; 13 dfs(i,rec+a[i]);//搜索与回溯 14 mark[i]=0; 15 } 16 } 17 } 18 int main() 19 { 20 scanf("%d",&n); 21 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 22 for(int i=1;i<n;i++) 23 { 24 for(int j=i+1;j<=n;j++) 25 { 26 int x; 27 scanf("%d",&x); 28 if(x) g[i][j]=1;//注意图是单向的 29 } 30 } 31 for(int i=1;i<=n;i++) 32 { 33 mark[i]=1; 34 dfs(i,a[i]); 35 mark[i]=0;//顶点逐个尝试 36 } 37 printf("%d",ans); 38 return 0; 39 }
上面那个代码非常简洁易懂,但仔细观察,我们会发现dfs中的参数rec并没有使用到运算中,所以我们可以做一个小小的优化(注意,时间复杂度并没有改变,只是方便后面的记忆化搜索):
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 using namespace std; 5 int n,ans,a[25],mark[25],g[25][25]; 6 int dfs(int x) 7 { 8 int rec=0; 9 for(int i=1;i<=n;i++) 10 { 11 if(!mark[i] && g[x][i]) 12 { 13 mark[i]=1; 14 rec=min(rec,dfs(i));//rec记录这个顶点出发能到达的最大值 15 mark[i]=0; 16 } 17 } 18 return rec+a[x];//返回rec与当前的地雷数量 19 } 20 int main() 21 { 22 scanf("%d",&n); 23 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 24 for(int i=1;i<n;i++) 25 { 26 for(int j=i+1;j<=n;j++) 27 { 28 int x; 29 scanf("%d",&x); 30 if(x) g[i][j]=1; 31 } 32 } 33 for(int i=1;i<=n;i++) 34 { 35 mark[i]=1; 36 ans=min(ans,dfs(i)); 37 mark[i]=0; 38 } 39 printf("%d",ans); 40 return 0; 41 }
但是这样的代码直接提交的话仍然会超时。为什么呢?很容易想到,我们在搜索的时候有很多的时间都在重复搜索,因此优化的办法是每找到当前点出发能到达的最大值就记录一下,下面可以直接调用。
路径储存的代码也放到完整代码中了(因为我太菜了讲不清楚,也许模拟一下会帮助理解)。
完整代码来啦
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 using namespace std; 5 int n,ans,a[25],mark[25],g[25][25]; 6 int dp[25],pre[25],pos; 7 int dfs(int x) 8 { 9 if(dp[x]!=-1) return dp[x]; 10 int rec=0; 11 for(int i=1;i<=n;i++) 12 { 13 if(!mark[i] && g[x][i]) 14 { 15 mark[i]=1; 16 if(dfs(i)>rec) rec=dfs(i),pre[x]=i; 17 mark[i]=0; 18 } 19 } 20 dp[x]=rec+a[x]; 21 return dp[x]; 22 } 23 int main() 24 { 25 memset(dp,-1,sizeof dp); 26 scanf("%d",&n); 27 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 28 for(int i=1;i<n;i++) 29 { 30 for(int j=i+1;j<=n;j++) 31 { 32 int x; 33 scanf("%d",&x); 34 if(x) g[i][j]=1; 35 } 36 } 37 for(int i=1;i<=n;i++) 38 { 39 mark[i]=1; 40 if(ans<dfs(i)) ans=dfs(i),pos=i; 41 mark[i]=0; 42 } 43 int now=pre[pos]; 44 printf("%d",pos); 45 while(now) 46 { 47 printf(" %d",now); 48 now=pre[now]; 49 } 50 printf(" "); 51 printf("%d",ans); 52 return 0; 53 }
以上是关于挖地雷(记忆化搜索)的主要内容,如果未能解决你的问题,请参考以下文章