1. 程式人生 > >codevs 3729 飛揚的小鳥 x

codevs 3729 飛揚的小鳥 x

次數 行為 否則 struct log d+ ive tar itl

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