挖地雷(記憶化搜尋)
原題:
挖地雷
時間限制: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
讓我們先把路徑的處理放一邊去。
先用二維陣列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 }