codevs 3729 飛揚的小鳥 x
3729 飛揚的小鳥
時間限制: 1 s 空間限制: 128000 KB 題目等級 : 黃金 Gold題目描述 Description 輸入描述 Input Description 輸出描述 Output Description
輸出文件名為 bird.out。
共兩行。
第一行,包含一個整數,如果可以成功完成遊戲,則輸出 1,否則輸出 0。
第二行,包含一個整數,如果第一行為 1,則輸出成功完成遊戲需要最少點擊屏幕數,
否則,輸出小鳥最多可以通過多少個管道縫隙。
樣例輸入 Sample Input 樣例輸出 Sample Output
【輸入輸出樣例說明】
如下圖所示,藍色直線表示小鳥的飛行軌跡,紅色直線表示管道。
數據範圍及提示 Data Size & Hint對於 30%的數據:5≤n≤10,5≤m≤10,k=0,保證存在一組最優解使得同一單位時間最多點擊屏幕 3 次;
對於 50%的數據:5≤n≤20,5≤m≤10,保證存在一組最優解使得同一單位時間最多點擊屏幕 3 次;
對於 70%的數據:5≤n≤1000,5≤m≤100;
對於 100%的數據: 5≤n≤10000, 5≤m≤1000, 0≤k<n, 0<X<m, 0<Y<m, 0<P<n, 0≤L<H ≤m,L +1<H。
思路:
首先想到設分f[i][j]表示到達地i行第j列所需要的最少點擊屏幕次數。轉移方程為
f[ i ][ j ]=min{f[ i-1 ][ j - k*x[i-1] ] + k} (1<= k <= j/x) 上升—— ①
f[ i ][ j ]=min{f[ i-1 ][ j + y[i-1] } ( j + y[i-1] <= m) 下降
顯然,下降可以O(1)轉移,主要問題在上升的轉移。
我們將上升的方程變一下:
f[ i ][ j - x[i-1] ]=min{f[ i-1 ][ (j - x[i-1]) - (k-1)*x[i-1] ] + k -1} ——②
這是 f[ i ][ j - x[i-1] ] 的轉移。
由 ② 化簡可得:
f[ i ][ j - x[i-1] ]=min{f[ i-1 ][ j - k*x[ i-1] ] + k -1}
消去f[ i-1 ][ j - k*x[ i-1] ]
f[ i ][ j ]= f[ i ][ j - x[ i-1 ] ]+1
於是就可以O(n*m)的時間內出解啦~
@大佬%%%
代碼:
#include <iostream> #include <cstdio> #include <cmath> #define INF 2100000000 #define LL long long using namespace std; const int Maxn = 10010; const int Maxm = 1010; int n,m,k; int x[Maxn],y[Maxn]; ///到達i行j列所需要的最少點擊屏幕的次數 int dp[Maxn][Maxm]; struct bird { int u,d; }b[Maxn]; int main() { scanf("%d%d%d",&n,&m,&k); for(int i=0; i<n; ++i) scanf("%d%d",&x[i],&y[i]); ///初始化各坐標(若不被更新,則說明該橫坐標上並不是管道) for(int i=1; i<=n; ++i) b[i].d=0,b[i].u=m+1; for(int i=0,p; i<k; ++i) { scanf("%d",&p); scanf("%d%d",&b[p].d,&b[p].u); } for(int i=1; i<=n; ++i) for(int j=0; j<=m; ++j) dp[i][j]=INF;///因為要取min,所以需要賦個極大值 ///因為j!=0,所以dp[0][0]=INF; dp[0][0]=INF; for(int i=1; i<=n; ++i) { ///暫時先不考慮管道的情況,進行更新dp數組 for(int j=x[i-1]; j<=m; ++j) { ///普通的轉移(由上一個(i-1)剛好能跳的f[i-1].x的坐標處的值+1進行轉移過來) dp[i][j]=min(dp[i][j],dp[i-1][j-x[i-1]]+1); ///見公式推出 dp[i][j]=min(dp[i][j],dp[i][j-x[i-1]]+1); ///因為不能夠超過m,會出現比較多種的轉移方程 if(j==m) for(int o=m-x[i-1]; o<=m; ++o) { ///位於i-1處,並且一跳能夠夠到m(j)的,用來進行i,j的更新 dp[i][j]=min(dp[i][j],dp[i-1][o]+1); ///見公式推出 dp[i][j]=min(dp[i][j],dp[i][o]+1); } } ///處理下落的情況,必須是合法的 for(int j=b[i].d+1; j<b[i].u; ++j) ///若合法 if(j+y[i-1]<=m) dp[i][j]=min(dp[i][j],dp[i-1][j+y[i-1]]); ///考慮當前管道縫隙的下界以下的地方是沒有dp值的,所以重新將其賦值為最大值 for(int j=1; j<=b[i].d; ++j) dp[i][j]=INF; ///上界以上的地方也是沒有dp值的 for(int j=m; j>=b[i].u; --j) dp[i][j]=INF; } int cnt=k,ans=INF; ///需要逆序枚舉 for(int i=n; i>=1; --i) { for(int j=1; j<=m; ++j) ans=min(ans,dp[i][j]); ///如果ans(i)逆序被更新了,那麽他一定就是最優解 ///因為勝利的結束條件是飛得遠一直到橫坐標為n if(ans!=INF) break; ///如果是管道 if(b[i].u<=m) cnt--; } ///在最後一個水管出現之前被break;那麽被更新的ans即為最優解 if(cnt==k) printf("1\n%d\n",ans); ///據題目要求輸出最多能通過幾個管道 else printf("0\n%d\n",cnt); return 0; }
codevs 3729 飛揚的小鳥 x