Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 72671 | Accepted: 26825 |
Description
1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
一個人可以從某個點滑向上下左右相鄰四個點之一,當且僅當高度減小。在上面的例子中,一條可滑行的滑坡爲24-17-16-1。當然25-24-23-...-3-2-1更長。事實上,這是最長的一條。
Input
Output
Sample Input
5 5 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
Sample Output
25
===================================================================================
題目大意:中文題不解釋。
解題思路:
老實說我是查簡單DP找到這道題的,但是做着做着,就做成深搜了,因爲傳統DP時間效率O(10^8),判超時。
先按DP思路來,DP需要打一張狀態表,橫軸是下降路徑的最後一個點,縱軸是爲路徑尋找新點的次數,狀態值是最後一個點爲點n時對應的最長路徑長度。
用上一行生成下一行時,逐個點嘗試上下左右方向是否有高度更低且更新後比原狀態值更大的點,符合條件則更新即覆蓋舊路徑。
一張圖最多100*100個點,故打DP表時間效率O(10^8),1000ms的邊緣時間效率本來應該在O(10^9)左右,既然超時看來是左非右了。
先看看我用DP最後的掙扎。
#include<cstdio>
const int N = 100;
int r, c, g[N][N], dp[2][N][N]; //N^4個int鐵定爆內存,於是用了滾動數組,只保存最後兩行DP狀態表
int main()
{
int i, j, k, oki = 0, newDp, i0, i1; //把多次使用的變量集中定義,能省點時間就省一點吧
bool ok;
scanf("%d%d", &r, &c);
for( i = 0 ; i < r ; i++ )
for( j = 0 ; j < c ; j++ )
scanf("%d", &g[i][j]);
for( i = 1 ; i < r*c ; i++ ) //逐行打DP狀態表
{
ok = true; //默認已沒有更優的路徑
for( j = 0 ; j < r ; j++ )
for( k = 0 ; k < c ; k++ )
{
i0 = (i-1)&1; //上一行DP狀態
i1 = i&1; //將生成的DP狀態
newDp = dp[i0][j][k]+1; //準備填寫的新狀態值
if( k > 0 && g[j][k-1] < g[j][k] && dp[i0][j][k-1] < newDp && dp[i1][j][k-1] < newDp )//高度更低的點,且可更新
{
dp[i1][j][k-1] = newDp; //更新狀態值
ok = false; //標記還可能有更優路徑
}
if( k < c-1 && g[j][k+1] < g[j][k] && dp[i0][j][k+1] < newDp && dp[i1][j][k+1] < newDp )
{
dp[i1][j][k+1] = newDp;
ok = false;
}
if( j > 0 && g[j-1][k] < g[j][k] && dp[i0][j-1][k] < newDp && dp[i1][j-1][k] < newDp )
{
dp[i1][j-1][k] = newDp;
ok = false;
}
if( j < r-1 && g[j+1][k] < g[j][k] && dp[i0][j+1][k] < newDp && dp[i1][j+1][k] < newDp )
{
dp[i1][j+1][k] = newDp;
ok = false;
}
}
if(ok)
break; //沒有更優路徑則結束打表
oki++; //打錶行數加1
}
int ans = 0;
for( i = 0 ; i < r ; i++ )
for( j = 0 ; j < c ; j++ )
if( dp[oki&1][i][j] > ans )
ans = dp[oki&1][i][j]; //在DP狀態表的最後一行找最優狀態值
printf("%d\n", ans+1);
return 0;
}
嗯太無情了。
那麼看看深搜,遍歷起點,向高度更低的各點擴散,無法擴散的點記狀態值即路徑長度爲1。
在回溯到起點的過程中每個點取四個方向擴散點的最大狀態值再加1作爲自己的狀態值。
遍歷起點時已經擁有狀態值的起點直接返回狀態值,不再重複搜索。不重複搜索也就是這道題用深搜在時間效率上巨大優勢的源頭了。
令人髮指的線性時間效率O(10^4)。
最後遍歷N*N的狀態值記錄數組,找最大值。
//Memory: 456K Time: 0MS
#include<cstdio>
const int N = 100; //下面的擴散就是搜索,因爲有平面圖,我覺得說擴散更形象好理解點
int r, c, g[N][N], dp[N][N]; //dp數組存各點最優狀態值
int DP(int j, int k)
{
if(dp[j][k]) //已被擴散過的點直接返回上一次算出的狀態值
return dp[j][k];
int t = 0;
if( k > 0 && g[j][k-1] < g[j][k] ) //向上下左右四個方向嘗試擴散
{
int tmp = DP(j,k-1); //進行擴散,或取上一次擴散得到的狀態值
if(tmp > t) //回溯時找到擴散點的最大狀態值
t = tmp;
}
if( k < c-1 && g[j][k+1] < g[j][k] )
{
int tmp = DP(j,k+1);
if(tmp > t)
t = tmp;
}
if( j > 0 && g[j-1][k] < g[j][k] )
{
int tmp = DP(j-1,k);
if(tmp > t)
t = tmp;
}
if( j < r-1 && g[j+1][k] < g[j][k] )
{
int tmp = DP(j+1,k);
if(tmp > t)
t = tmp;
}
return dp[j][k] = t+1; //給點賦狀態值,回溯
}
int main()
{
scanf("%d%d", &r, &c);
for( int i = 0 ; i < r ; i++ )
for( int j = 0 ; j < c ; j++ )
scanf("%d", &g[i][j]);
for( int j = 0 ; j < r ; j++ ) //遍歷起點確保每個點都擴散過
for( int k = 0 ; k < c ; k++ )
DP(j,k);
for( int i = 0 ; i < r ; i++ ) //遍歷dp數組找最優狀態值即最大值
for( int j = 0 ; j < c ; j++ )
if( dp[i][j] > dp[0][0] )
dp[0][0] = dp[i][j];
printf("%d\n", dp[0][0]);
return 0;
}
我深搜函數都沒用dfs起名可見我對DP還懷有深深地執念與遺憾。
哦,poj破百了。