1. 程式人生 > 實用技巧 >挖地雷(記憶化搜尋)

挖地雷(記憶化搜尋)

原題:

挖地雷

時間限制:1Sec記憶體限制: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("\n");
51     printf("%d",ans);
52 return 0;
53 }